Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net inheritance and method overloading

Here is a code sample:

class Program
{
    static void Main(string[] args)
    {
        var obj = new DerivedClass();
        obj.SomeMethod(5);
    }
}

class BaseClass
{
    internal void SomeMethod(int a) { }
}

class DerivedClass : BaseClass
{
    internal void SomeMethod(long a) { }
}

Can somebody explain me why is a method from derived class called (instead of base class method)? I need a detailed explanation for this situation. I will be grateful for links to any useful articles.

Thanks.

like image 589
Nick P. Avatar asked Sep 30 '14 09:09

Nick P.


People also ask

Can we use method overloading in inheritance in C#?

In C#, just like in C++, there is no overload resolution between class Base and class Derived. Also, there is no overloading across scopes and derived class scopes are not an exception to this general rule.

Can method overloading occurs in inheritance?

Overloading can happen in same class as well as parent-child class relationship whereas overriding happens only in an inheritance relationship.

Can we overload method in child class in C#?

You can achieve method overriding using inheritance. Virtual and Override keywords are used to achieve method overriding. In the above example, I have created two same name methods in the BaseClass as well as in the ChildClass.

How method overriding is different from method overloading in C#?

Overloading is determined at compile time and is static. Overriding is determined at runtime and is dynamic. Overloading concerns giving a method with the same name different parameters. Overriding concerns defining a different implementation of the same method in inherited classes.


1 Answers

The precise wording and location varies with different versions of the spec, but for example here one can read:

The set of candidate methods for the method invocation is constructed. Starting with the set of methods associated with M, which were found by a previous member lookup (§7.3), the set is reduced to those methods that are applicable with respect to the argument list A. The set reduction consists of applying the following rules to each method T.N in the set, where T is the type in which the method N is declared:

If N is not applicable with respect to A (§7.4.2.1), then N is removed from the set.

If N is applicable with respect to A (§7.4.2.1), then all methods declared in a base type of T are removed from the set.

So, given that we have obj of type DerivedClass then the set of member methods contains void SomeMethod(long) from DerivedClass and void SomeMethod(int) from BaseClass.

Both of these methods are applicable, and indeed void SomeMethod(int) is a better overload match, but because of the rule in the last sentence quoted above, once it is found that void SomeMethod(long) is applicable, all methods from base classes are removed from the set of candidates, meaning that void SomeMethod(int) is no longer considered.

Okay, that's the technical reason in terms of the spec. What is the design reason behind that being in the spec in the first place?

Well, imagine that BaseClass started out defined as:

public class BaseClass
{
}

If the rest of the code was the same, then it's pretty obvious that the call to obj.SomeMethod(5) should call the only so-named method that existed.

Now consider if after that code was written, the method void SomeMethod(int) was added to BaseClass. And consider indeed that this could be in a different assembly to DerivedClass, and by a separate author.

Now the meaning of the call to SomeMethod() has changed. Worse, it's changed or not depending on which updates a given machine has or hasn't got applied. (And worse again, since return type isn't used in C# overload resolution, it's changed in a way that could produce a compile error in already-compiled code: A full breaking change).

The rule of excluding methods defined in a base class if there are overload candidates from a more derived class allows for greater assurance that one is calling the method one intended to call, in the face of future changes. (Of course you might be surprised if you'd intended the base classes methods to be called, but then at the time of coding you could catch that problem and use a cast to ensure the behaviour you wanted was what resulted).

A consequence of this that can be surprising to some though is in:

class Program
{
    static void Main(string[] args)
    {
        var obj = new DerivedClass();
        obj.SomeMethod(5);
    }
}
class BaseClass
{
    public virtual void SomeMethod(int a) { Console.WriteLine("Base"); }
}
class DerivedClass : BaseClass
{
    public override void SomeMethod(int a) { Console.WriteLine("Defined in Base, overriden in Derived"); }
    public void SomeMethod(long a) { Console.WriteLine("Derived"); }
}

This outputs Derived, because this rule applies according to where the method is declared, even if there is an implementation from an override.

(Another reason for the rule working as it does, is that when it's converted into CIL the call will contain information about the class it's declared in. The rule here is the simplest possible way of doing things. That said; 1) A similar logic applied in the design of CIL and 2) the above made this a feature of CIL for the C# people to work with, rather than one to work against).

like image 101
Jon Hanna Avatar answered Oct 29 '22 15:10

Jon Hanna