【问题标题】:Why is this test expression an error?为什么这个测试表达式是错误的?
【发布时间】:2013-10-16 19:24:39
【问题描述】:

我想了解为什么 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 而无需强制转换。但是为什么我不能在不强制转换的情况下检查这两个对象是否相同?他们明明实现了相同的接口,那为什么会报错呢?

【问题讨论】:

  • 通用接口不等同于...
  • 如果编译了,该怎么办?

标签: c# generics interface polymorphism


【解决方案1】:

您可以使用Object.ReferenceEqualsObject.Equals 来检查它们是否是同一个对象。

但是,由于您的约束(IAIB 接口)不强制类型必须是引用类型,因此不能保证可以使用相等运算符。

【讨论】:

    【解决方案2】:

    假设您使用实现 IA 的值类型 X 构造 T。

    做什么

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

    当被称为testFunction&lt;X&gt;(new X(whatever)) 时执行?

    T 是 X,X 实现 IA,所以隐式转换框 obj 到 obj2。

    相等运算符现在将值类型 X 与编译时类型 IA 的装箱副本进行比较。运行时类型是编译器不关心的盒装 X;该信息被忽略。

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

    它不能使用 reference 比较语义,因为这意味着 obj 也必须被装箱。它不会装箱到相同的引用,所以这总是是假的,这看起来很糟糕。

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

    没有编译器可以合理选择的相等语义,因此这是非法的。

    现在,如果您将 T 限制为引用类型,那么第一个反对意见就会消失,编译器可以合理地选择引用相等。如果这是您的意图,则将 T 限制为引用类型。

    【讨论】:

      【解决方案3】:

      稍微扩展一下里德的答案(这当然是正确的):

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

      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);

      [...]

      预定义的引用类型相等运算符需要以下之一:

      • 两个操作数都是已知类型的值,该类型是 reference-type 或文字 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
          {
      
          }
      }
      

      【讨论】:

        【解决方案4】:

        试试

            if (obj.Equals(obj2))
        

        IA 没有实现任何 == 运算符。

        【讨论】:

          【解决方案5】:

          啊,感谢 Reed Copsey 的说明。

          我刚刚还发现你可以像这样将“class”放在 where 子句中。

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

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

          【讨论】:

          • 但是,如果进行参考比较很重要,那么最好将其标注出来并使用ReferenceEquals,以使代码更清晰。
          猜你喜欢
          • 2016-11-26
          • 2021-05-18
          • 1970-01-01
          • 1970-01-01
          • 2015-11-14
          • 2016-07-22
          • 2013-12-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多