C# 使用GetHashCode比较相同的匿名类型安全吗?
给定两个相同的匿名类型对象:C# 使用GetHashCode比较相同的匿名类型安全吗?,c#,.net,comparison,anonymous-types,gethashcode,C#,.net,Comparison,Anonymous Types,Gethashcode,给定两个相同的匿名类型对象: {msg:"hello"} //anonType1 {msg:"hello"} //anonType2 并假设它们没有解析为相同的类型(例如,它们可能在不同的程序集中定义) 此外,假设在编译时,我无法获得一个的结构(比如说anonType1),因为API只公开对象 因此,为了比较它们,我想到了以下技术: 使用反射获取anonType1上的msg属性以进行比较 将anonType1转换为dynamic类型,并在动态成员上引用.msg,以进行比较 比较每个对象上.Ge
{msg:"hello"} //anonType1
{msg:"hello"} //anonType2
并假设它们没有解析为相同的类型(例如,它们可能在不同的程序集中定义)
此外,假设在编译时,我无法获得一个的结构(比如说anonType1
),因为API只公开对象
因此,为了比较它们,我想到了以下技术:
anonType1
上的msg
属性以进行比较anonType1
转换为dynamic
类型,并在动态成员上引用.msg
,以进行比较.GetHashCode()
的结果我的问题是:使用选项3安全吗?也就是说,假设
.GetHashcode()
实现在.NET framework的当前版本和所有未来版本中,对于相同结构但不同的匿名类型总是返回相同的值,这是否合理?有趣的问题。规范定义了等于
和GetHashcode
(请注意规范中的输入错误!)方法将对相同类型的实例起作用,但是没有定义实现。碰巧,当前的MS C编译器使用幻数来实现这一点,比如种子-1134271262
和乘法器-1521134295
。但这不是规范的一部分。从理论上讲,这可能会在C#编译器版本之间发生根本性的变化,而且它仍然能够满足需要。因此,如果这两个程序集不是由同一个编译器编译的,则无法保证。事实上,编译器在每次编译时都会想出一个新的种子值是“有效的”(但不太可能)
就个人而言,我会考虑使用IL或Expression
技术来实现这一点。使用Expression
,按名称比较形状相似的对象非常容易
关于信息,我还研究了mcs(Mono编译器)如何实现GetHashCode
,并且它是不同的;它不使用种子和乘法器,而是使用种子、异或、乘法器、移位和加法的组合。因此,由Microsoft和Mono编译的相同类型将有非常不同的GetHashCode
static class Program {
static void Main() {
var obj = new { A = "abc", B = 123 };
System.Console.WriteLine(obj.GetHashCode());
}
}
- 单声道:-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);
}
}
}
使用系统;
使用System.Linq;
使用System.Linq.Expressions;
运用系统反思;
福班
{
公共字符串A{get;set;}
public int B;//注意一个字段!
静态void Main()
{
var obj1=new{A=“abc”,B=123};
var obj2=newfoo{A=“abc”,B=123};
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1,obj2));//True
obj1=new{A=“abc”,B=123};
obj2=newfoo{A=“abc”,B=456};
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1,obj2));//False
obj1=new{A=“def”,B=123};
obj2=newfoo{A=“abc”,B=456};
Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1,obj2));//False
}
}
公共静态类MemberwiseComparer
{
公共静态对象等效(对象x、对象y)
{
//处理空值。。。
如果(x==null)返回y==null;
如果(y==null)返回false;
返回值等于TIMPL((动态)x,(动态)y);
}
专用静态布尔值等于TIMPL(TX x,TY y)
{
返回一个等价的计算值(x,y);
}
静态类AreEquivalentCache
{
静态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);
表达式体=null;
ParameterExpression x=表达式.参数(类型(TX),“x”),
y=表达式参数(typeof(TY),“y”);
foreach(成员中的var成员)
{
var thisTest=Expression.Equal(
表达式.PropertyOrField(x,成员),
表达式.PropertyOrField(y,成员));
body=body==null?此测试
:表达式AndAlso(正文,本测试);
}
如果(body==null)body=Expression.Constant(true);
func=Expression.Lambda(body,x,y).Compile();
}
私有静态只读Func Func;
公共静态布尔值(TX x,TY y)
{
返回函数(x,y);
}
}
}
注意:我添加了一个基于表达式的memberwise comparer,如果有一个规范精确定义了编译器必须创建的匿名类,那么互操作性将是微不足道的。如果没有编译器生成的类应该是什么样子的规范,我认为即使这些类想要互操作,它们也无法互操作。某些任意类的属性的名称和值与匿名类的名称和值匹配这一事实并不意味着前者的实例应与后者的实例进行比较。@supercat另一方面,如果两个对象之间不是语义上的伪等价,则不太可能比较这两个对象。对于所有非空的X
和Y
类型,其Equals(object)
,((object)X)。Equals(Y)
和((object)Y)。Equals(X)
应始终返回相同的值,毫无例外。让一个类型报告它的实例
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);
}
}
}