Boon
Boon

Reputation: 41470

Overridden virtual method in C#

In "The C# Programming Language" book Eric Lippert mentioned this:

A subtle point here is that an overridden virtual method is still considered to be a method of the class that introduced it, and not a method of the class that overrides it.

What is the significance of this statement? Why does it matter if the overridden virtual method is considered to be a method of the class that introduced it (or otherwise) since the overridden method will never be called unless you are dealing with the derived class?

Upvotes: 4

Views: 7264

Answers (3)

Michael Liu
Michael Liu

Reputation: 55339

Here's the full quote from the book:

A subtle point here is that an overridden virtual method is still considered to be a method of the class that introduced it, and not a method of the class that overrides it. The overload resolution rules in some cases prefer members of more derived types to those in base types; overriding a method does not "move" where that method belongs in this hierarchy.

At the very beginning of this section, we noted that C# was designed with versioning in mind. This is one of those features that helps prevent "brittle base-class syndrome" from causing versioning problems.

The full quote makes it clear that Eric Lippert is talking specifically about method overloading, not just about how virtual methods work.

As an example, consider the following program:

class Base
{
    public virtual void M2(int i) { }
}

class Derived : Base
{
    public void M1(int i) { Console.WriteLine("Derived.M1(int)"); }
    public void M1(float f) { Console.WriteLine("Derived.M1(float)"); }

    public override void M2(int i) { Console.WriteLine("Derived.M2(int)"); }
    public void M2(float f) { Console.WriteLine("Derived.M2(float)"); }

    public static void Main()
    {
        Derived d = new Derived();
        d.M1(1);
        d.M2(1);
    }
}

I think many developers would be surprised that the output is

Derived.M1(int)
Derived.M2(float)

Why would d.M2(1) invoke Derived.M2(float) even though Derived.M2(int) is a better match?

When the compiler is determining what the M1 in d.M1(1) refers to, the compiler sees that both M1(int) and M1(float) are introduced in Derived, so both overloads are applicable candidates. The compiler selects M1(int) over M1(float) as the best match for the integer argument 1.

When the compiler is determining what the M2 in d.M2(1) refers to, the compiler sees that M2(float) is introduced in Derived and is an applicable candidate. According to the overload resolution rules, "methods in a base class are not candidates if any method in a derived class is applicable". Because M2(float) is applicable, this rule prevents M2(int) from being a candidate. Even though M2(int) is a better match for the integer argument and even though it's overridden in Derived, it's still considered to be a method of Base.

Upvotes: 5

supercat
supercat

Reputation: 81115

Understanding that an overridden virtual method belongs to the class which introduces it, rather than the class that overrides it, makes it easier to understand the way class members are bound. Except when using dynamic objects, all bindings in C# are resolved at compile time. If a BaseClass declares a virtual method foo, and DerivedClass:BaseClass overrides foo, code which attempts to call foo on a variable of type BaseClass will always be bound to virtual method "slot" BaseClass.foo, which will in turn point to the actual DerivedClass.foo method.

This understanding can be especially important when dealing with generics, since in C#, unlike C++, members of generic types are bound according to the generics' constraints, rather than according to the concrete generic types. For example, suppose there were a SubDerivedClass:DerivedClass which created a new virtual method foo(), and one defined a method DoFoo<T>(T param) where T:BaseClass {param.foo();}. The param.foo() call would be bound to BaseClass.foo even if the method were invoked as DoFoo<SubDrivedClass>(subDerivedInstance). If the parameter were cast to SubDerivedClass before invoking foo, the call would be bound to SubDrivedClass.foo(), but the compiler can't tell when producing DoFoo<T> that T will be anything more specific than BaseClass, it can't bind to anything that doesn't exist in BaseClass.

Incidentally, there are times it would be useful if a class could simultaneously override a base-class member and create a new one; for example, given an abstract base class ReadableFoo with some abstract read-only property, it would be helpful if a class MutableFoo could both provide an override for that property and define a read-write property with the same name. Unfortunately, .net does not allow that. Given such a restriction, the best approach may be for ReadableFoo to provide a concrete non-virtual read-only property which calls an protected abstract method with a different name to get the value. That way, a derived class could shadow the read-only property with a read-write one (that would call the same virtual method for reading, or a new (possibly virtual) method for writing.

(following is untested)

class BaseClass
{
    public virtual void foo() {Console.WriteLine("BaseClass.Foo");
}
class DerivedClass:BaseClass
{
    public override void foo() {Console.WriteLine("Derived.Foo");
}
class SubDerivedClass:DerivedClass
{
    public new virtual void foo() {Console.WriteLine("SubDerived.Foo");
}
class MegaDerivedClass:SubDerivedClass
{
    public override void foo() {Console.WriteLine("MegaDerived.Foo");
}
void DoFoo1<T>(T param) where T:BaseClass 
    {
        param.foo();
}
void DoFoo1<T>(T param) where T:SubDerivedClass 
    {
        param.foo();
}
void test(void)
{
    var megaDerivedInstance = new MegaDerivedClass();
    DoFoo1<MegaDerivedClass>(megaDerivedInstance);
    DoFoo2<MegaDerivedClass>(megaDerivedInstance);
}

A SubDerivedClass has two virtual foo() methods: BaseClass.foo() and SubDerivedClass.foo(). A MegaDerivedClass has those same two methods. Note that classes derived from SubDerivedClass() which attempt to override foo will override SubDerivedClass.foo() and will not affect BaseClass.foo(); with the declarations as above, no derivative of SubDerivedClass can override BaseClass.Foo. Note also that casting an instance of SubDerivedClass or a subclass thereof to DerivedClass or BaseClass will expose the BaseClass.foo virtual method for calling.

Incidentally, if the method declaration in SubDerivedClass had been friend new virtual void foo() {Console.WriteLine("SubDerived.Foo");, it would be impossible for other classes within the same assembly to override BaseClass.foo() (since any attempt to override foo() would override SubDerivedClass.foo()), but classes outside the assembly which derive from SubDerivedClass wouldn't see SubDerivedClass.foo() and could thus override BaseClass.foo().

Upvotes: 0

Guffa
Guffa

Reputation: 700152

It matters when you have a reference of one type pointing to an object of a different type.

Example:

public class BaseClass {
  public virtual int SomeVirtualMethod() { return 1; }
}

public class DerivedClass : BaseClass {
  public override int SomeVirtualMethod() { return 2; }
}

BaseClass ref = new DerivedClass();
int test = ref.SomeVirtualMethod(); // will be 2

Because the virtual method is a member of the base class, you can call the overriding method with a reference of the base class type. If it wasn't, you would need a reference of the derived type to call the overriding method.

When you are shadowing a method instead of overriding it, the shadowing method is a member of the derived class. Depending on the type of the reference you will be calling the original method or the shadowing method:

public class BaseClass {
  public int SomeMethod() { return 1; }
}

public class DerivedClass : BaseClass {
  public new int SomeMethod() { return 2; }
}

BaseClass ref = new DerivedClass();
int test = ref.SomeMethod(); // will be 1

DerivedClass ref2 = ref;
int test2 = ref2.SomeMethod(); // will be 2

Upvotes: 7

Related Questions