为什么这个测试表达式会出错?

4
我想了解为什么C#语言决定将这个测试表达式作为一个错误。
interface IA { }
interface IB { }
class Foo : IA, IB { }
class Program
{
    static void testFunction<T>(T obj) where T : IA, IB
    {
        IA obj2 = obj;

        if (obj == obj2) //ERROR
        {

        }
    }
    static void Main(string[] args)
    {
        Foo myFoo = new Foo();
        testFunction(myFoo);
        Console.ReadLine();
    }
}

在testFunction中,我可以创建一个名为obj2的对象并将其隐式设置为obj,而无需进行强制转换。但是,为什么我不能在不进行强制转换的情况下检查这两个对象是否相同呢?它们显然实现了相同的接口,那么为什么会出错?

1
常见的接口并不意味着等价... - nispio
如果编译通过,它应该做什么? - Servy
5个回答

11
假设您使用值类型X构建了T,该类型实现了IA接口。

“What does”意思是什么呢?

static void testFunction<T>(T obj) where T : IA
{
    IA obj2 = obj;
    if (obj == obj2) //ERROR

当以testFunction<X>(new X(whatever))的形式调用时,应该执行什么操作?

T是X,X实现了IA,因此隐式转换将obj装箱成obj2。

现在等号操作符正在比较一个值类型X和编译时类型为IA的装箱副本。编译器不关心运行时类型是否为一个装箱X;这些信息会被忽略。

它应该使用什么比较语义?

它不能使用引用比较语义,因为这意味着obj也必须被装箱。它不会装到同一个引用中,因此这总是错误的,这似乎很糟糕。

它不能使用比较语义,因为编译器没有依据来确定它应该使用哪种类型的值比较语义!在编译时,它不知道未来所选择的T类型是否会提供重载的==运算符,即使它确实存在,该运算符也不太可能以IA作为其操作数之一。

没有合理可选的相等语义,因此这是不合法的。

现在,如果您把T限制为引用类型,则第一个异议就消失了,编译器可以合理地选择引用相等性。如果这是您的意图,则将T限制为引用类型。


11
您可以使用Object.ReferenceEqualsObject.Equals来检查它们是否为同一对象。
但是,由于您的约束(IAIB接口)并不强制类型必须是引用类型,因此无法保证等号运算符可以使用。

6

稍微扩展一下Reed的答案(它当然是正确的):

请注意,以下代码在编译时会导致相同的错误:

Guid g = Guid.NewGuid(); // a value type
object o = g;

if (o == g) // ERROR
{
}

C#语言规范(§7.10.6)如下所述:
预定义的引用类型相等性操作符为:
bool operator ==(object x, object y);
bool operator!=(object x, object y);
预定义的引用类型相等性运算符要求满足以下条件之一:
两个操作数都是已知为"引用类型"的值,或者是文本null。此外,从任一操作数的类型到另一个操作数的类型存在显式引用转换(§6.2.4)。
一个操作数是类型T的值,其中T是类型参数,另一个操作数是文本null。此外,T没有值类型约束。
除非满足上述条件之一,否则会出现绑定时错误。这些规则的显著影响包括:
预定义的引用类型相等性运算符不允许比较值类型操作数。因此,除非结构类型声明自己的相等性运算符,否则无法比较该结构类型的值。
预定义的引用类型相等运算符永远不会对其操作数执行装箱操作。执行这样的装箱操作是毫无意义的,因为对新分配的装箱实例的引用必然与所有其他引用不同。
在您的代码示例中,未将T限制为引用类型,因此出现编译时错误。但是,通过声明T必须是引用类型,可以修复您的示例:
static void testFunction<T>(T obj) where T : class, IA, IB
{
    IA obj2 = obj;

    if (obj == obj2) // compiles fine
    {

    }
}

1
尝试
    if (obj.Equals(obj2))

IA不实现任何==运算符。


0

感谢Reed Copsey的提醒。

我刚刚发现你可以在where子句中像这样放置"class"。

    static void testFunction<T>(T obj) where T : class, IA, IB
    {
        IA obj2 = obj;

        if (obj == obj2)
        {

        }
    }

现在它是一个引用类型,而且它可以工作了!:-)


2
但是,如果进行引用比较很重要,最好明确指出并仍然使用ReferenceEquals,以使代码更清晰。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接