对匿名类型调用 Equals 取决于对象是在哪个程序集中创建的

Calling Equals on anonymous type depends on which assembly the object was created in

我在 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

完全相同的代码returns如果匿名对象来自另一个程序集,则结果不同。

这是 C# 中的错误、实现细节还是我完全不了解的内容?

P.S。如果我在快速观察中评估表达式(运行时为真,快速观察为假),也会发生同样的事情:

这是一个你不懂的实现细节。

如果您使用匿名类型,编译器必须生成一个新类型(具有难以描述的名称,例如<>f__AnonymousType0<<A>j__TPar>),并在使用它的程序集中生成该类型。

对于该程序集中具有相同结构的匿名类型的所有使用,它将使用相同的生成类型。但是,每个程序集都有自己的匿名类型定义:无法跨程序集共享它们。当然,正如您发现的那样,解决这个问题的方法是将它们作为 object.

传递

此限制是无法公开匿名类型的主要原因之一:您不能 return 从方法中获取它们,将它们作为字段等。如果您这样做会导致各种问题可以在集会之间传递它们。

您可以在 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<..>,并使用编译器魔法来假装他们的属性的名称不是 Item1Item2