.Net inheritance and method overloading

前端 未结 5 969
清酒与你
清酒与你 2021-02-20 11:15

Here is a code sample:

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

class          


        
相关标签:
5条回答
  • 2021-02-20 11:43

    From the C# language reference:

    7.5.5 Function member invocation

    This section describes the process that takes place at run-time to invoke a particular function member. It is assumed that a binding-time process has already determined the particular member to invoke, possibly by applying overload resolution to a set of candidate function members.

    For purposes of describing the invocation process, function members are divided into two categories:

    • Static function members. <snip>
    • Instance function members. These are instance methods, instance property accessors, and indexer accessors. Instance function members are either non-virtual or virtual, and are always invoked on a particular instance. The instance is computed by an instance expression, and it becomes accessible within the function member as this (§7.6.7). The run-time processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:
      • If M is a static function member: <snip>
      • If M is an instance function member declared in a value-type: <snip>
      • If M is an instance function member declared in a reference-type:
        • E is evaluated. If this evaluation causes an exception, then no further steps are executed.
        • The argument list is evaluated as described in §7.5.1.
        • If the type of E is a value-type, <snip>
        • The value of E is checked to be valid. If the value of E is null, a System.NullReferenceException is thrown and no further steps are executed.
        • The function member implementation to invoke is determined:
          • If the binding-time type of E is an interface, <snip>
          • Otherwise, if M is a virtual function member, <snip>
          • Otherwise, M is a non-virtual function member, and the function member to invoke is M itself.
        • The function member implementation determined in the step above is invoked. The object referenced by E becomes the object referenced by this.

    What's more in 1.6.6.4 Virtual, override and abstract methods, we have

    When a virtual method is invoked, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke. In a nonvirtual method invocation, the compile-time type of the instance is the determining factor.

    So what happens is that when you compile your code, the type of the variable you are using determines what method is called.

    public class A { public void WhoAreYou() { Console.WriteLine("A"); } }
    
    public class B : A  { public void WhoAreYou() { Console.WriteLine("B"); } }
    
    internal class Program
    {
    
    private static void Main(string[] args)
    {
        (new B() as A).WhoAreYou(); // "A"
        (new B()).WhoAreYou(); // "B"
    
        Console.ReadLine();
    }
    

    Please note that the compiler will warn you of a potential problem since the method that will be called differs depending on the type you use to define the class instance.

    0 讨论(0)
  • 2021-02-20 11:52

    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).

    0 讨论(0)
  • 2021-02-20 11:52

    What I understand is that, since overriding/hiding is not applied, method of derived class is called as it is instantiated in main().

    In Method Overriding: Base class reference variable pointing to child class object will invoke the overridden method in child class. “Override” keyword is used in derived class method signature.

    In Method Hiding: Base class reference variable pointing to child class object will invoke the hidden method in base class. “New” keyword is used in derived class method signature.

    0 讨论(0)
  • 2021-02-20 11:55
    var obj = new DerivedClass();
    

    var keyword is just syntactic sugar in C#; this is essentially the same as:

    DerivedClass obj = new DerivedClass();
    

    hence you're calling DerrivedClass.SomeMethod, which is just the behaviour you're experiencing. You would see a difference if you defined your variable like this:

    BaseClass obj = new DerivedClass();
    
    0 讨论(0)
  • 2021-02-20 12:00

    Edit after comments: True, I probably did not answer the exact question properly, so let me try now:

    The method call in the original code matches the signature of both methods (both in the base, and the derived class), since the parameter 5 could be either an int or a long in this case. The base method however, is not marked as virtual (which would allow overriding), and the "derived" method is not really derived, since it is not marked with override.

    NOTE however, that even if you did mark it as override, you would get an error, since in reality, the two method signatures are not equivalent: One takes an int, while the other takes a long type. This will result in a compile-time error with the messsage: "no suitable method found to override".

    The rest should hopefully become clear if you read the rest of my original answer below.


    Original answer:

    There are several things to note here:

    1) Your methods have different signatures; one takes a long, and the other takes an int

    2) You haven't marked your methods virtual or override.

    An edited version of your code with some comments might make it more clear how this stuff works:

    internal class Program
    {
        private static void Main(string[] args)
        {
    
            var obj = new DerivedClass();
            // That is the same as:
            //DerivedClass obj = new DerivedClass();
    
            // Will call the base method, since that now matches the
            // signature (takes an int parameter). DerivedClass simply
            // does not HAVE a method with that signature on it's own:
            obj.SomeMethod(5); // will output "base with int"
    
            // Now call the other method, which IS defined in DerivedClass, 
            // by appending an "l", to mark this as a Long:
            obj.SomeMethod(5l); // Will output "derived"
    
            // This would call the base method directly
            var obj2 = new BaseClass();
            obj2.SomeMethod(5l); 
    
            Console.ReadKey();
        }
    }
    
    internal class BaseClass
    {
        internal void SomeMethod(int a)
        {
            Console.WriteLine("base with int");
        }
    
        // Added method for the example:
        // Note that "virtual" allows it to be overridden
        internal virtual void SomeMethod(long a)
        {
            Console.WriteLine("base with long");
        }
    }
    
    internal class DerivedClass : BaseClass
    {
        // Note: Overrides the base method now
        internal override void SomeMethod(long a)
        {
            Console.WriteLine("derived");
        }
    }
    
    0 讨论(0)
提交回复
热议问题