Equals() and inheritance

A

Alex Cohn

In C++, there is an easy technique to provide an overloaded Equals() method.
A straightforward translation to C# causes a stack overflow. Why does
b.Equals(ba) in the snippet below not understand that it should call (ba as
B).Equals(b) inside?

Thanks in advance,

Alex

code sample follows:

class A
{
protected int a = 1;

public A() {}
public override bool Equals(object obj)
{
return obj.Equals(this);
}
public virtual bool Equals(A obj)
{
return obj.a == this.a;
}
}

class B : A
{
private static int cnt = 0;
protected int b = 2;

public B() { a = cnt++; }

public override bool Equals(A obj)
{
return obj.Equals(this);
}
public virtual bool Equals(B obj)
{
return obj.b == this.b;
}
}

static void Main(string[] args)
{
A a = new A();
A a1 = new A();
B b = new B();
B b1 = new B();
A ba = new B();

Console.WriteLine(a.Equals(a1)); // OK, prints true
Console.WriteLine(a.Equals(b)); // OK, prints false
Console.WriteLine(b.Equals(a)); // OK, prints false
Console.WriteLine(b.Equals(b1)); // OK, prints true
Console.WriteLine(b.Equals(ba)); // stack overflow
Console.WriteLine(ba.Equals(b)); // stack overflow
}

PS I know a workaround for this problem, but it looks ugly to me:

B.Equals should be written as:

public override bool Equals(A obj)
{
B Bobj = obj as B;
if (Bobj != null)
return Bobj.Equals(this);
else
return obj.Equals(this);
}

This solution does not satisfy me becasue it is equivalent to writing the
case of obj being of class B explicitly, like this:

public override bool Equals(A obj)
{
B Bobj = obj as B;
if (Bobj != null)
return this.b == Bobj.b;
else
return obj.Equals(this);
}
 
J

Jon Skeet [C# MVP]

In C++, there is an easy technique to provide an overloaded Equals() method.
A straightforward translation to C# causes a stack overflow. Why does
b.Equals(ba) in the snippet below not understand that it should call (ba as
B).Equals(b) inside?

Overloading is done at compile time, so for
b.Equals(ba)
the compiler only knows that b is of type B, and ba is of type A.

It therefore calls B.Equals(A).

Now let's look at the code for B.Equals(A). The compiler only knows
about obj as A, and so the line
obj.Equals(this) will call A.Equals(A) (there's no overload for
A.Equals(B)). You've overridden that in B.Equals(A), hence the
recursion.

At what point would you expect it to behave differently?

Jon
 
A

Alex Cohn

In C++, inheritance works through a virtual table. Each object refers to a
table of virtual functions. The overloaded Equals() for object of type B is
in the object's table. Therefore, even if the object is "known" to the
compiler as type A reference, the call will peform the B::Equals() call. I am
puzzled if C# resolves all overloads at compile time.

Actually, it does not. In the following example, the compiler can not know
which method to call, but the output is correct:

class A { public override string ToString() { return "A"; } }
class B: A { public override string ToString() { return "B"; } }
static void Main(string[] args) {
A qq = null; if (args.length == 1) qq = new A(); else qq = new B();
Console.WriteLine(qq.ToString());
}

Alex
 
J

Jon Skeet [C# MVP]

In C++, inheritance works through a virtual table. Each object refers to a
table of virtual functions. The overloaded Equals() for object of type B is
in the object's table. Therefore, even if the object is "known" to the
compiler as type A reference, the call will peform the B::Equals() call. I am
puzzled if C# resolves all overloads at compile time.

You're confusing overloading and overriding. Overriding is performed
at execution time, but overloading is performed at compile time. I
believe C++ works the same way.

Jon
 
A

Alex Cohn

I am sorry if I caused confusion with my usage of words "overload" and
"override". However, the compiler does not care much about the terms. I still
do not understand why the expected override for ToString() is used, but the
expected override for Equals() is not.
 
J

Jon Skeet [C# MVP]

I am sorry if I caused confusion with my usage of words "overload" and
"override". However, the compiler does not care much about the terms.

It certainly does - if you try using "overload" as a keyword instead
of "override" you'll find it cares an awful lot.
I still do not understand why the expected override for ToString() is used, but the
expected override for Equals() is not.

Because you're expecting an *overload* of Equals rather than an
*override*.

When A.Equals(A) is called, that will result in the override
B.Equals(A) being executed. It will *never* result (directly) in
B.Equals(B) being executed, because that's a different signature.

Jon
 
A

Alex Cohn

Thank you for your patience. I have tried to answer your last reply with a
more descriptive example, and finally figured out what went wrong in my head.

Alex
 
J

Jon Skeet [C# MVP]

Thank you for your patience. I have tried to answer your last reply with a
more descriptive example, and finally figured out what went wrong in my head.

Goodo :) I wasn't sure how I was going to explain it any other way -
it's one of those problems where it's very easy to miscommunicate by
referring to different methods which have the same name, because
that's inherent in the situation...

Jon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top