给出两个相同的匿名类型对象:
{msg:"hello"} //anonType1
{msg:"hello"} //anonType2
并假设它们没有解析为相同类型(例如,它们可能在不同的程序集中定义)
anonType1.Equals(anonType2); //false
此外,假设在编译时,我无法得到一个结构(比如anonType1
),因为API只公开object
因此,为了比较它们,我想到了以下技术:
msg
上的anonType1
属性进行比较。anonType1
投放到dynamic
类型并引用动态成员上的.msg
进行比较.GetHashCode()
对每个对象的结果。我的问题是:使用选项3是否安全?即是否明智地假设.GetHashcode()
实现将始终为.NET框架的当前版本和所有未来版本中的缩进结构但不同的匿名类型返回相同的值?
答案 0 :(得分:5)
有趣的问题。规范定义了Equals
和GetHashcode
(注意规范中的拼写错误!)方法将对相同类型的实例表现,但是没有定义实现。碰巧的是,当前的MS C#编译器使用幻数来实现这一点,如-1134271262
的种子和-1521134295
的乘数。但不是规范的一部分。从理论上说,它可以在C#编译器版本之间彻底改变,它仍然可以满足它的需要。因此,如果2个程序集不是由同一个编译器编译的,则无法保证。实际上,编译器在每次编译时都会想出一个新的种子值是“有效的”(但不太可能)。
就个人而言,我会考虑使用IL或Expression
技术来做到这一点。使用Expression
可以很容易地按名称比较类似形状的对象。
有关信息,我还看了mcs
(Mono编译器)如何实现GetHashCode
, 它是不同的 ;而不是种子和乘数,它使用种子,xor,乘数,移位和加法的组合。因此,Microsoft和Mono编译的相同类型将具有非常不同的GetHashCode
。
static class Program {
static void Main() {
var obj = new { A = "abc", B = 123 };
System.Console.WriteLine(obj.GetHashCode());
}
}
基本上,我认为你不能保证这一点。
怎么样:
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);
}
}
}