c# - Is it safe to use GetHashCode to compare identical Anonymous types? -
given 2 identical anonymous type objects:
{msg:"hello"} //anontype1 {msg:"hello"} //anontype2
and assume haven't resolved same type (e.g. might defined in different assemblies)
anontype1.equals(anontype2); //false
furthermore, assume @ compile time, can't structure of 1 (say anontype1
) because api exposes object
so, compare them, thought of following techniques:
- use reflection
msg
property onanontype1
comparison. - cast
anontype1
dynamic
type , reference.msg
on dynamic member comparison - compare result of
.gethashcode()
on each object.
my question is: safe use option 3? i.e. sensible assume .gethashcode()
implementation return same value indentically-structured, different anonymous types in current , future versions of .net framework?
interesting question. specification defines equals
, gethashcode
(note typo in specification!) methods behave instances of same type, implementation not defined. happens, current ms c# compiler implements using magic numbers seed of -1134271262
, multiplier of -1521134295
. is not part of specification. theoretically change radically between c# compiler versions , still meet needs to. if 2 assemblies not compiled same compiler, there no guarantee. indeed, "valid" (but unlikely) compiler think new seed value every time compiles.
personally, @ using il or expression
techniques this. comparing similarly-shaped objects member-wise name easy expression
.
for info, i've looked @ how mcs
(the mono compiler) implements gethashcode
, , it different; instead of seed , multiplier, uses combination of seed, xor, multiplier, shifts , additions. same type compiled microsoft , mono have very different gethashcode
.
static class program { static void main() { var obj = new { = "abc", b = 123 }; system.console.writeline(obj.gethashcode()); } }
- mono: -2077468848
- microsoft: -617335881
basically, not think can guarantee this.
how about:
using system; using system.linq; using system.linq.expressions; using system.reflection; class foo { public string { get; set; } public int b; // note field! static void main() { var obj1 = new { = "abc", b = 123 }; var obj2 = new foo { = "abc", b = 123 }; console.writeline(memberwisecomparer.areequivalent(obj1, obj2)); // true obj1 = new { = "abc", b = 123 }; obj2 = new foo { = "abc", b = 456 }; console.writeline(memberwisecomparer.areequivalent(obj1, obj2)); // false obj1 = new { = "def", b = 123 }; obj2 = new foo { = "abc", b = 456 }; console.writeline(memberwisecomparer.areequivalent(obj1, obj2)); // false } } public static class memberwisecomparer { public static bool areequivalent(object x, object y) { // deal 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); } } }
Comments
Post a Comment