泛型类中的字符串相等

时间:2017-06-05 08:28:57

标签: c# generics

如果T是字符串,则泛型类中 == 等于之间有什么区别。

我准备了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

2 个答案:

答案 0 :(得分: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

如果value1等于value2,你可以看到它正在使用ceq 推送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)的显式使用,这是字符串值检查比较。

答案 1 :(得分:2)

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

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

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         

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

string.Equals