Search code examples
c#.netgenericsstring-interning

Operator overloading in Generic Methods


This code snippet is from C# in Depth

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

The output of the above code snippet is

True 
False

When the main method is changed to

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

The output of the above code snippet is

True 
True

I cannot fathom why ?

EDIT: Once you understand string-interning following questions don't apply.

How are the parameters received at the Generic method AreReferencesEqual in the second code snippet ?

What changes to the string type when it is concatenated to make the == operator not call the overloaded Equals method of the String type ?


Solution

  • On the case of strings, you probably don't intend to use reference equality. To access equality and inequality in generic methods, your best bet it:

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

    i.e.

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

    This still uses the overloaded Equals, but handles nulls etc too. For inequality, this handles nulls, and both IComparable<T> and IComparable.

    For other operators, see MiscUtil.


    Re the question; in the case of:

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

    You get true, true because the compiler and runtime is designed to be efficient with strings; any literals that you use are "interned" and the same instance is used every time in your AppDomain. The compiler (rather than runtime) also does the concat if possible - i.e.

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

    is exactly the same code as the previous example. There is no difference at all. However, if you force it to concatenate strings at runtime, it assumes they are likely to be short-lived, so they are not interned/re-used. So in the case:

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

    you have 4 strings; "Jon" (interned), "My name is " (interned), and two different instances of "My name is Jon". Hence == returns true and reference equality returns false. But value-equality (EqualityComparer<T>.Default) would still return true.