我在C#(.net core 3.1)中发现了一个非常奇怪的行为,用于比较我无法解释的匿名对象。
据我了解,为匿名对象调用Equals
使用结构相等性比较(例如,检查here)。示例:
public static class Foo
{
public static object GetEmptyObject() => new { };
}
static async Task Main(string[] args)
{
var emptyObject = new { };
emptyObject.Equals(new { }); // True
emptyObject.Equals(Foo.GetEmptyObject()); // True
}
看起来很正确。但是如果我将“ Foo”移到另一个程序集,情况将完全不同!
emptyObject.Equals(Foo.GetEmptyObject()); // False
如果匿名对象来自另一个程序集,则完全相同的代码将返回不同的结果。
这是C#中的错误,实现细节还是我完全不理解的东西?
P.S。如果我在快速监视中对表达式求值(在运行时为true,在快速监视中为false),也会发生相同的事情:
这是您不了解的实现细节。
如果使用匿名类型,则编译器必须生成一个新类型(名称难以置信,例如<>f__AnonymousType0<<A>j__TPar>
,并在使用它的程序集中生成该类型。
对于该程序集中的所有匿名类型,它将使用相同的生成类型。但是,每个程序集都有其自己的匿名类型定义:无法在程序集之间共享它们。
此限制是无法公开匿名类型的主要原因之一:您无法从方法中将其返回,将其用作字段等。如果您可以在程序集之间传递它们,则会引起各种问题。
您可以在SharpLab中看到该文件,其中:
var x = new { A = 1 };
使此类型在同一程序集中生成:
internal sealed class <>f__AnonymousType0<<A>j__TPar>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly <A>j__TPar <A>i__Field;
public <A>j__TPar A
{
get
{
return <A>i__Field;
}
}
[DebuggerHidden]
public <>f__AnonymousType0(<A>j__TPar A)
{
<A>i__Field = A;
}
[DebuggerHidden]
public override bool Equals(object value)
{
global::<>f__AnonymousType0<<A>j__TPar> anon = value as global::<>f__AnonymousType0<<A>j__TPar>;
if (anon != null)
{
return EqualityComparer<<A>j__TPar>.Default.Equals(<A>i__Field, anon.<A>i__Field);
}
return false;
}
[DebuggerHidden]
public override int GetHashCode()
{
return -1711670909 * -1521134295 + EqualityComparer<<A>j__TPar>.Default.GetHashCode(<A>i__Field);
}
[DebuggerHidden]
public override string ToString()
{
object[] obj = new object[1];
<A>j__TPar val = <A>i__Field;
obj[0] = ((val != null) ? val.ToString() : null);
return string.Format(null, "{{ A = {0} }}", obj);
}
}
ValueTuple
在要匿名定义类型但仍要在程序集之间传递类型时遇到了相同的挑战,并以不同的方式解决了这一问题:通过在BCL中定义ValueTuple<..>
,并使用编译器魔术师来假装它们的属性使用其他名称比Item1
,Item2
等