tags:

views:

142

answers:

4

I was surprised recently to discover that the compiler is apparently not strict about comparing interface references and am wondering why it works this way.

Consider this code:

class Program
{
    interface I1 {}
    interface I2 {}
    class C1 : I1 {}
    class C2 : I2 {}

    static void Main(string[] args)
    {
        C1 c1 = new C1();
        C2 c2 = new C2();

        I1 i1 = c1;
        I2 i2 = c2;

        bool x = c1 == c2;
        bool y = i1 == i2;
    }
}

The compiler says that I can't compare c1 == c2, which follows. The types are totally unrelated. Yet, it does permit me to compare i1 == i2. I would expect it to error here with a compile-time failure but I was surprised to find out that you can compare any interface to any other and the compiler will never complain. I could compare, for example (I1)null == (IDisposable)null and no problem.

Are interfaces not objects? Are they a special type of reference? My expectation would be that a == would result in either a straight reference compare or a call into the concrete class's virtual Equals.

What am I missing?

+12  A: 

I suppose this was done in a such way because you can have a type inheriting both interfaces and for this case such comparison could be useful:

interface I1 {}
interface I2 {}
class C1 : I1, I2 {}

So in the first case compiler definitely knows that objects are different but in second case they might be not.

Andrew Bezzub
Explanation works for me +1
Gurdas Nijor
A: 

My expectation would be that a == would result in either a straight reference compare or a call into the concrete class's virtual Equals.

This is true, but the compiler doesn't know this. This would be determined at run-time.

Jordan
+4  A: 

It is described very well in the C# Language Specification, chapter 7.9.6 "Reference type equality operators":

The predefined reference type equality operators are:

bool operator ==(object x, object y);
bool operator !=(object x, object y);

The operators return the result of comparing the two references for equality or non-equality.

Since the predefined reference type equality operators accept operands of type object, they apply to all types that do not declare applicable operator == and operator != members. Conversely, any applicable user-defined equality operators effectively hide the predefined reference type equality operators.

The predefined reference type equality operators require one of the following:
• Both operands are reference-type values or the literal null. Furthermore, a standard implicit conversion (§6.3.1) exists from the type of either operand to the type of the other operand.
• One operand is a value of type T where T is a type-parameter and the other operand is the literal null. Furthermore T does not have the value type constraint.

Unless one of these conditions are true, a compile-time error occurs. Notable implications of these rules are:
• It is a compile-time error to use the predefined reference type equality operators to compare two references that are known to be different at compile-time. For example, if the compile-time types of the operands are two class types A and B, and if neither A nor B derives from the other, then it would be impossible for the two operands to reference the same object. Thus, the operation is considered a compile-time error.

The last paragraph is why you get the error.

Hans Passant
Note that you are quoting from the C# 3.0 specification, which contains a typo which is relevant to this question. "implicit" above should read "explicit".
Eric Lippert
+10  A: 

First off, note that Hans is quoting the correct section of the specification, but that the edition of the specification he is quoting has a typo which is relevant to your question. The corrected C# 4 specification says:

The predefined reference type equality operators require one of the following:

(1) Both operands are a value of a type known to be a reference-type or the literal null. Furthermore, an explicit reference conversion exists from the type of either operand to the type of the other operand.

(2) One operand is a value of type T where T is a type-parameter and the other operand is the literal null. Furthermore T does not have the value type constraint.

Unless one of these conditions are true, a binding-time error occurs.

This explains your observation. There is an explicit reference conversion between any two interfaces because any two instances of two different interfaces could be referencing the same object. There could be a class C3 which implements both I1 and I2, and you could be doing a reference comparison of the same instance of C3, one converted to I1 and the other converted to I2.

Eric Lippert
Lots of good answers here, but yours most completely answers my question. Thank you.
Scott Bilas