Given two identical anonymous type objects:
{msg:"hello"} //anonType1
{msg:"hello"} //anonType2
And assume that they haven't resolved to the same type (e.g. they might be defined in different assemblies)
anonType1.Equals(anonType2); //false
Furthermore, assume that at compile time, I can't get the structure of one (say anonType1
) because the API only exposes object
So, to compare them, I thought of the following techniques:
msg
property on anonType1
for comparison.anonType1
to a dynamic
type and reference .msg
on the dynamic member for comparison.GetHashCode()
on each object.My question is: Is it safe to use Option 3? I.e. is it sensible to assume that the .GetHashcode()
implementation will always return the same value for indentically-structured, but different anonymous types in the current and all future versions of the .NET framework?
If you're implementing a reference type, you should consider overriding the Equals method if your type looks like a base type, such as Point, String, BigNumber, and so on. Override the GetHashCode method to allow a type to work correctly in a hash table.
A hash code is a numeric value which is used to insert and identify an object in a hash-based collection. The GetHashCode method provides this hash code for algorithms that need quick checks of object equality.
Interesting question. The specification defines that Equals
and GetHashcode
(note the typo in the specification!) methods will behave for instances of the same type, however the implementation is not defined. As it happens, the current MS C# compiler implements this using magic numbers like a seed of -1134271262
and a multiplier of -1521134295
. But that is not part of the specification. Theoretically that could change radically between C# compiler versions and it would still meet what it needs to. So if the 2 assemblies are not compiled by the same compiler, there is no guarantee. Indeed, it would be "valid" (but unlikely) for the compiler to think up a new seed value every time it compiles.
Personally, I would look at using IL or Expression
techniques to do this. Comparing similarly-shaped objects member-wise by name is fairly easy to do with Expression
.
For info, I've also looked at how mcs
(the Mono compiler) implements GetHashCode
, and it is different; instead of seed and multiplier, it uses a combination of seed, xor, multiplier, shifts and additions. So the same type compiled by Microsoft and Mono will have very different GetHashCode
.
static class Program {
static void Main() {
var obj = new { A = "abc", B = 123 };
System.Console.WriteLine(obj.GetHashCode());
}
}
Basically, I do not think you can guarantee this.
How about:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class Foo
{
public string A { get; set; }
public int B; // note a field!
static void Main()
{
var obj1 = new { A = "abc", B = 123 };
var obj2 = new Foo { A = "abc", B = 123 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // True
obj1 = new { A = "abc", B = 123 };
obj2 = new Foo { A = "abc", B = 456 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False
obj1 = new { A = "def", B = 123 };
obj2 = new Foo { A = "abc", B = 456 };
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False
}
}
public static class MemberwiseComparer
{
public static bool AreEquivalent(object x, object y)
{
// deal with nulls...
if (x == null) return y == null;
if (y == null) return false;
return AreEquivalentImpl((dynamic)x, (dynamic)y);
}
private static bool AreEquivalentImpl<TX, TY>(TX x, TY y)
{
return AreEquivalentCache<TX, TY>.Eval(x, y);
}
static class AreEquivalentCache<TX, TY>
{
static AreEquivalentCache()
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
var xMembers = typeof(TX).GetProperties(flags).Select(p => p.Name)
.Concat(typeof(TX).GetFields(flags).Select(f => f.Name));
var yMembers = typeof(TY).GetProperties(flags).Select(p => p.Name)
.Concat(typeof(TY).GetFields(flags).Select(f => f.Name));
var members = xMembers.Intersect(yMembers);
Expression body = null;
ParameterExpression x = Expression.Parameter(typeof(TX), "x"),
y = Expression.Parameter(typeof(TY), "y");
foreach (var member in members)
{
var thisTest = Expression.Equal(
Expression.PropertyOrField(x, member),
Expression.PropertyOrField(y, member));
body = body == null ? thisTest
: Expression.AndAlso(body, thisTest);
}
if (body == null) body = Expression.Constant(true);
func = Expression.Lambda<Func<TX, TY, bool>>(body, x, y).Compile();
}
private static readonly Func<TX, TY, bool> func;
public static bool Eval(TX x, TY y)
{
return func(x, y);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With