使用GetHashCode比较相同的匿名类型是否安全?

Jam*_*man 10 .net c# comparison anonymous-types gethashcode

给出两个相同的匿名类型对象:

{msg:"hello"} //anonType1
{msg:"hello"} //anonType2
Run Code Online (Sandbox Code Playgroud)

并假设它们没有解析为相同类型(例如,它们可能在不同的程序集中定义)

anonType1.Equals(anonType2); //false
Run Code Online (Sandbox Code Playgroud)

此外,假设在编译时,我无法得到一个(比如说anonType1)的结构,因为API只暴露object

所以,为了比较它们,我想到了以下技术:

  1. 使用反射来获取msg属性以anonType1进行比较.
  2. anonType1dynamic类型和参考.msg动态构件上用于比较
  3. 比较.GetHashCode()每个对象的结果.

我的问题是:使用选项3是否安全?即,假设.GetHashcode()在.NET框架的当前版本和所有未来版本中,实现将始终为缩进结构但不同的匿名类型返回相同的值,这是明智的吗?

Mar*_*ell 5

有趣的问题.规范定义了EqualsGetHashcode(注意规范中的拼写错误!)方法将对相同类型的实例表现,但是没有定义实现.碰巧的是,当前的MS C#编译器使用魔术数字来实现这一点,如种子-1134271262和乘数-1521134295.但这不是规范的一部分.从理论上说,它可以在C#编译器版本之间彻底改变,它仍然可以满足它的需要.因此,如果2个程序集不是由同一个编译器编译的,则无法保证.实际上,编译器每次编译时都会想出一个新的种子值是"有效的"(但不太可能).

就个人而言,我会考虑使用IL或Expression技术来做到这一点.按名称比较类似形状的对象是相当容易的Expression.

有关信息,我还研究了mcs(Mono编译器)如何实现GetHashCode,它是不同的 ; 而不是种子和乘数,它使用种子,xor,乘数,移位和加法的组合.因此,微软和Mono编译的相同类型将有很大不同GetHashCode.

static class Program {
    static void Main() {
        var obj = new { A = "abc", B = 123 };
        System.Console.WriteLine(obj.GetHashCode());
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 单声道:-2077468848
  • 微软:-617335881

基本上,我认为你不能保证这一点.


怎么样:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)