通用方法中的运算符重载

6
这段代码片段来自《C#深入浅出》。
    static bool AreReferencesEqual<T>(T first, T second)
        where T : class
    {
        return first == second;
    }

    static void Main()
    {
        string name = "Jon";
        string intro1 = "My name is " + name;
        string intro2 = "My name is " + name;
        Console.WriteLine(intro1 == intro2);
        Console.WriteLine(AreReferencesEqual(intro1, intro2));
    }

以上代码片段的输出结果是:
True 
False

当主方法被更改为:
    static void Main()
    {
        string intro1 = "My name is Jon";
        string intro2 = "My name is Jon";
        Console.WriteLine(intro1 == intro2);
        Console.WriteLine(AreReferencesEqual(intro1, intro2));
    }

以上代码片段的输出结果是:
True 
True

我无法理解为什么?

编辑:一旦您了解了字符串内部化,以下问题就不适用。

第二个代码片段中的通用方法AreReferencesEqual如何接收参数?

当将字符串连接起来以使==运算符不调用String类型的重载Equals方法时,字符串类型会发生什么变化?


7
请记住,_泛型_不是_模板_。编译器只对==运算符进行一次重载分析,每个泛型构造都使用该分析结果。我们不会为Compare<Foo>、Compare<Bar>和Compare<string>分别进行分析。 我们只进行一次分析。当比较已知为任何类的T与T时,我们唯一能做的就是让==意味着“按引用比较”。因此,无论T是什么,它始终意味着“按引用比较”。 - Eric Lippert
3个回答

13

在处理字符串时,你可能不想使用引用相等性。 要在通用方法中访问相等和不相等,最好的选择是:

EqualityComparer<T>.Default.Equals(x,y); // for equality
Comparer<T>.Default.Compare(x,y); // for inequality

即。
static bool AreValuesEqual<T>(T first, T second)
    where T : class
{
    return EqualityComparer<T>.Default.Equals(first,second);
}

这仍然使用了重载的Equals,但也处理了空值等情况。对于不相等的情况,它处理了空值,以及IComparable<T>IComparable
关于其他运算符,请参见MiscUtil
关于这个问题;在以下情况下:
    string intro1 = "My name is Jon";
    string intro2 = "My name is Jon";
    Console.WriteLine(intro1 == intro2);
    Console.WriteLine(AreReferencesEqual(intro1, intro2));

你得到的是truetrue,因为编译器和运行时都被设计成对字符串高效;你使用的任何字面值都会被“内部化”,并且在你的 AppDomain 中每次都使用相同的实例。如果可能的话,编译器(而不是运行时)也会进行连接 - 例如。
    string intro1 = "My name is " + "Jon";
    string intro2 = "My name is " + "Jon";
    Console.WriteLine(intro1 == intro2);
    Console.WriteLine(AreReferencesEqual(intro1, intro2));

这段代码与之前的示例完全相同。它们没有任何区别。然而,如果您在运行时强制将其连接成字符串,它会假定它们可能是短暂的,所以它们不会被intern/re-use。因此,在以下情况下:

    string name = "Jon";
    string intro1 = "My name is " + name;
    string intro2 = "My name is " + name;
    Console.WriteLine(intro1 == intro2);
    Console.WriteLine(AreReferencesEqual(intro1, intro2));

你有4个字符串;“Jon”(已经存储在内存池中),“My name is”(也已经存储在内存池中),以及两个不同的实例,“My name is Jon”。因此,使用==运算符返回true,但引用相等性返回false。但是,值相等性(EqualityComparer<T>.Default)仍然会返回true。

@Marc:谢谢,那很简洁明了。 - abhilash

5
今天学到了一个新的东西。
我想是在回答Jon的某个问题时,我尝试去回答。
当你使用连接构建字符串时,==会对匹配值的两个字符串返回true,但它们不指向同一引用(这让我感到困惑,因为字符串插补应该会起作用。Jon指出,字符串插补仅适用于常量或文字)。
在通用版本中,它调用object.ReferenceEquals(与==不同。在字符串的情况下,==执行值比较)。
因此,连接的版本返回false,而常量(文字字符串)版本返回true。
编辑:我认为Jon必须在更好的方式下解释这个问题 :)
懒惰的我,已经买了这本书,但还没有开始看 :(

1
不需要我自己解释了 - Marc 做得很好 :) (你的解释也很好)。 - Jon Skeet

2

这与通用方法无关,而是字符串的实例化。

在main函数的第一个版本中:

string name = "Jon";
string intro1 = "My name is " + name;
string intro2 = "My name is " + name;

这段代码创建了4个字符串。其中两个是编译时常量,分别为"Jon"和"My name is"。然而,在初始化intro1和intro2时,编译器无法确定name始终为jon,因此在运行时为每个intro1和intro2创建一个新的字符串。

在第二个版本中:

string intro1 = "My name is Jon";
string intro2 = "My name is Jon";

你只有一个字符串,那就是一个编译时常量:"My name is Jon",你将这个字符串分配给了intro1和intro2,这就是为什么
AreReferencesEqual(intro1, intro2)

在第一种情况下返回false,在第二种情况下返回true。

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