【问题标题】:String equality in generic class泛型类中的字符串相等
【发布时间】:2017-06-05 08:28:57
【问题描述】:

如果 T 是字符串,泛型类中的 ==Equals 有什么区别。

我准备了带有 2 个测试的示例代码。 TestMethod1 在第二次断言失败,但 TestMethod2 通过。

源代码:

[TestFixture]
public class Test
{
    class Foo<T> where T : class
    {
        public static bool Foo1(T item1, T item2)
        {
            return item1 == item2;
        }

        public static bool Foo2(T item1, T item2)
        {
            return item1.Equals(item2);
        }
    }

    [TestCase]
    public void TestMethod1()
    {
        var name = "str" + 1;
        // passes
        Assert.IsTrue(Foo<string>.Foo2(name, "str1"));
        // fails !
        Assert.IsTrue(Foo<string>.Foo1(name, "str1"));
    }

    [TestCase]
    public void TestMethod2()
    {
        // passes
        Assert.IsTrue(Foo<string>.Foo2("str1", "str1"));
        // also passes!
        Assert.IsTrue(Foo<string>.Foo1("str1", "str1"));
    }
}

相关问题: c# compare two generic values

【问题讨论】:

  • 如果你写 var name = "str" + 1; 会发生什么?在测试方法 2 中,而不是使用实际的字符串
  • 在您的第二个测试方法中,这些字符串文字将被实习,因此将是同一个对象。这可能是您正在观察的行为的一部分。
  • 同样,第二次断言失败。 @Matt Jones 是的,知道这一点。
  • 这些答案对您有帮助吗?

标签: c# generics


【解决方案1】:

正如马特已经提到的,你的一半问题是实习。
另一半是编译器不知道您的类型参数是string 并将其视为某种引用类型,因此它调用object 的方法。
但是不要相信它,让我们来看看IL。

ceq 这里告诉我们没有== 运算符重载,因此对boxed 参数执行引用检查。

Foo`1.Foo1:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  box         Test+Foo<>.T
IL_0007:  ldarg.1     
IL_0008:  box         Test+Foo<>.T
IL_000D:  ceq         
IL_000F:  stloc.0     
IL_0010:  br.s        IL_0012
IL_0012:  ldloc.0     
IL_0013:  ret         

另一方面,在这里我们可以看到callvirtSystem.Object.Equals,并且在运行时它将被解析为对任何实现实际类型参数的调用,即string.Equals

Foo`1.Foo2:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  box         Test+Foo<>.T
IL_0007:  ldarg.1     
IL_0008:  box         Test+Foo<>.T
IL_000D:  callvirt    System.Object.Equals
IL_0012:  stloc.0     
IL_0013:  br.s        IL_0015
IL_0015:  ldloc.0     
IL_0016:  ret 

【讨论】:

    【解决方案2】:

    在这种情况下使用== 会解析为System.Reference.Equals

    .Equals 是一个虚方法,因此将使用覆盖的版本,在这种情况下,它将是字符串的内容比较。

    第一个测试将局部变量与文字(内部)字符串进行比较。

    第二个测试比较相同的实习字符串,所以==Equals 都返回true。

    如果您将TestMethod1() 更改为:

            Assert.IsTrue(Foo<string>.Foo1(name, name));
    

    它会通过,因为你在比较同一个对象,所以引用相等返回 true。

    编辑:如果我们添加这个方法:

        public static bool Foo3(string item1, string item2)
        {
            return item1 == item2;
        }
    

    那么下面的测试会返回true

        Assert.IsTrue(Foo<string>.Foo3(name, "str1"));
    

    因为在这种情况下 C# 会将 == 解析为字符串值检查。

    这个问题确实是 this 的重复,具有额外的一般复杂性。

    编辑 2: 好的,是时候深入了解 MSIL。 Foo1 看起来像这样:

    .method public hidebysig static bool  Foo1(!T item1,
                                               !T item2) cil managed
    {
      // Code size       20 (0x14)
      .maxstack  2
      .locals init ([0] bool V_0)
      IL_0000:  nop
      IL_0001:  ldarg.0
      IL_0002:  box        !T
      IL_0007:  ldarg.1
      IL_0008:  box        !T
      IL_000d:  ceq
      IL_000f:  stloc.0
      IL_0010:  br.s       IL_0012
      IL_0012:  ldloc.0
      IL_0013:  ret
    } // end of method Foo`1::Foo1
    

    你可以在这里看到它正在使用ceq,它是如果 value1 等于 value2,则推送 1(int32 类型),否则推送 0。

    这里是Foo2

    .method public hidebysig static bool  Foo2(!T item1,
                                               !T item2) cil managed
    {
      // Code size       23 (0x17)
      .maxstack  2
      .locals init ([0] bool V_0)
      IL_0000:  nop
      IL_0001:  ldarg.0
      IL_0002:  box        !T
      IL_0007:  ldarg.1
      IL_0008:  box        !T
      IL_000d:  callvirt   instance bool [mscorlib]System.Object::Equals(object)
      IL_0012:  stloc.0
      IL_0013:  br.s       IL_0015
      IL_0015:  ldloc.0
      IL_0016:  ret
    } // end of method Foo`1::Foo2
    

    这使用System.Object::Equals - 这是虚拟方法并且行为如此,在参数的具体类型中被覆盖 - 在本例中为string

    Foo3(我添加了,它在声明的string 参数上使用==)看起来像这样:

    .method public hidebysig static bool  Foo3(string item1,
                                               string item2) cil managed
    {
      // Code size       13 (0xd)
      .maxstack  2
      .locals init ([0] bool V_0)
      IL_0000:  nop
      IL_0001:  ldarg.0
      IL_0002:  ldarg.1
      IL_0003:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                     string)
      IL_0008:  stloc.0
      IL_0009:  br.s       IL_000b
      IL_000b:  ldloc.0
      IL_000c:  ret
    } // end of method Foo`1::Foo3
    

    这里我们观察到System.String::op_Equality(string, string) 的显式使用,这是字符串值检查比较。

    【讨论】:

    • Assert.IsTrue(name == "str1"); 也会通过,即使引用不同。 String 有自己的== operator,我问为什么它不在泛型类中使用。
    • 因为Assert.IsTrue(name == "str1"); 将显式声明的string 与内部字符串进行比较,所以它使用== 的字符串值检查版本。在您的通用方法中,正在使用 == 的对象引用版本,因为这就是 C# 解决它的方式。
    猜你喜欢
    • 2023-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-15
    相关资源
    最近更新 更多