@hidden_pingvin

Почему переопределение в C# работает так?

//Пример 1:
	public class Class2 
    {
        public string GetValue(int a, int b)
        {
            return "base";
        }
    }

    public class Class3 : Class2
    {
        public string GetValue(int a, int b, bool becauseNeeded = true)
        {
            return "child";
        }
    }
	
	//...
	//вызов где-то в точке входа
	void Main()
	{
		string res = c.GetValue(1,2);
	}

В данном примере (Пример 1) происходит перегрузка метода базового класса, но при вызове этого метода вызывается метод дочернего класса. Не должен ли был быть вызван метод базового класса, так как это было не переопределение?
Если же не использовать наследование (Пример 2), то вызывается метод, возвращающий "base".
//Пример 2:
	public class Class3 
    {
        public string GetValue(int a, int b)
        {
            return "base";
        }
        public string GetValue(int a, int b, bool becauseNeeded = true)
        {
            return "child";
        }
    }

Почему работает так?
  • Вопрос задан
  • 327 просмотров
Решения вопроса 1
@majstar_Zubr
C++, C#, gamedev
public class Class2
    {
        public string GetVal(int a, int b)
        {
            return "base";
        }

        public override string ToString()
        {
            return "_Class2_";
        }
    }


public class Class3 : Class2
    {

        
        public string GetVal(int a, int b, bool defaultpar = true)
        {
            return "child with bool";
        }

        public new string GetVal(int a, int b)
        {
            return "child";
        }

        public override string ToString()
        {
            return "_Class3_";
        }
    }


class Program
    {
        static void Main(string[] args)
        {
            Class2 obj_reference_c2 = new Class2();
            Class3 obj_reference_c3 = new Class3();
            Class2 obj_reference_assigned = obj_reference_c3;
            Class2 obj_reference_casted = (Class2)obj_reference_c3;

            Console.WriteLine($"Вызов - {obj_reference_c2} c параметрами 1, 2 : {obj_reference_c2.GetVal(1, 2)}");
            Console.WriteLine($"Вызов - {obj_reference_c3} c параметрами 1, 2 : {obj_reference_c3.GetVal(1, 2)}");
            Console.WriteLine($"Вызов {obj_reference_c3} c параметрами 1, 2 true: {obj_reference_c3.GetVal(1, 2, true)}");

            Console.WriteLine($"Вызов Class3 присвоенного по ссылке типа Class2 - {obj_reference_assigned} c параметрами 1, 2: {obj_reference_assigned.GetVal(1, 2)}");
            Console.WriteLine($"Вызов Class3 приведенного к типу Class2 - {obj_reference_casted} c параметрами 1, 2: {obj_reference_casted.GetVal(1, 2)}");

            /*      
            Вариант 1: в Class3 метод public string GetVal(int a, int b) {return "child";} закомментирован

                Вызов - _Class2_ c параметрами 1, 2 : base
                Вызов - _Class3_ c параметрами 1, 2 : child with bool
                Вызов _Class3_ c параметрами 1, 2 true: child with bool
                Вызов Class3 присвоенного по ссылке типа Class2 - _Class3_ c параметрами 1, 2: base
                Вызов Class3 приведенного к типу Class2 - _Class3_ c параметрами 1, 2: base

             */

            /*
              Вариант 2: в Class3 метод public string GetVal(int a, int b) {return "child";} существует и прячет унаследованный член Class2.GetVal(int,int)
            
                Вызов - _Class2_ c параметрами 1, 2 : base
                Вызов - _Class3_ c параметрами 1, 2 : child
                Вызов _Class3_ c параметрами 1, 2 true: child with bool
                Вызов Class3 присвоенного по ссылке типа Class2 - _Class3_ c параметрами 1, 2: base
                Вызов Class3 приведенного к типу Class2 - _Class3_ c параметрами 1, 2: base

                в подобном случае требуется явно указывать в объявлении модификатор new:
                   public new string GetVal(int a, int b) {return "child";}
             */
            Console.ReadLine();
        }
    }


В вашем примере 1 не происходит перегрузка метода базового класса.
Метод базового класса можно перекрыть ( public new string Foo() ) или переопределить, если базовый метод виртуальный.

Перегруженные методы существуют только в одной области видимости, которая в C# ограничена классами.

С точки зрения .Net, методы с сигнатурами string _ (int,int) и string _ (int,int,bool) разные, но синтаксически, в случае, если в методе string _ (int,int,bool) третий параметр снабжается параметром по умолчанию, не существует явного способа вызова, кроме Named Arguments.

public class Class2
    {
        public string GetVal(int a, int b)
        {
            return "base";
        }

        public override string ToString()
        {
            return "_Class2_";
        }
    }

    public class Class3 : Class2
    {
        public new string GetVal(int a, int b)
        {
            return "child";
        }

        public string GetVal(int A, int B, bool defaultpar = true)
        {
            return "child with bool";
        }

        public override string ToString()
        {
            return "_Class3_";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Class2 obj_reference_c2 = new Class2();
            Class3 obj_reference_c3 = new Class3();
            Class2 obj_reference_assigned = obj_reference_c3;
            Class2 obj_reference_casted = (Class2)obj_reference_c3;

            Console.WriteLine($"Вызов - {obj_reference_c2} c параметрами 1, 2 : {obj_reference_c2.GetVal(1, 2)}");
            Console.WriteLine($"Вызов - {obj_reference_c3} c параметрами 1, 2 : {obj_reference_c3.GetVal(1, 2)}");
            Console.WriteLine($"Вызов - {obj_reference_c3} c параметрами 1, 2 : {obj_reference_c3.GetVal(A:1, B:2)}");
            Console.WriteLine($"Вызов {obj_reference_c3} c параметрами 1, 2 true: {obj_reference_c3.GetVal(1, 2, true)}");

            Console.WriteLine($"Вызов Class3 присвоенного по ссылке типа Class2 - {obj_reference_assigned} c параметрами 1, 2: {obj_reference_assigned.GetVal(1, 2)}");
            Console.WriteLine($"Вызов Class3 приведенного к типу Class2 - {obj_reference_casted} c параметрами 1, 2: {obj_reference_casted.GetVal(1, 2)}");

            /*
               
            Вариант 1Б: в Class3 метод public string GetVal(int a, int b) {return "child";} закомментирован

                Вызов - _Class2_ c параметрами 1, 2 : base
                Вызов - _Class3_ c параметрами 1, 2 : child with bool
                Вызов - _Class3_ c параметрами 1, 2 : child with bool
                Вызов _Class3_ c параметрами 1, 2 true: child with bool
                Вызов Class3 присвоенного по ссылке типа Class2 - _Class3_ c параметрами 1, 2: base
                Вызов Class3 приведенного к типу Class2 - _Class3_ c параметрами 1, 2: base

             */

            /*

           Вариант 2Б: в Class3 метод public string GetVal(int a, int b) {return "child";} существует и прячет базовый

                Вызов - _Class2_ c параметрами 1, 2 : base
                Вызов - _Class3_ c параметрами 1, 2 : child
                Вызов - _Class3_ c параметрами 1, 2 : child with bool
                Вызов _Class3_ c параметрами 1, 2 true: child with bool
                Вызов Class3 присвоенного по ссылке типа Class2 - _Class3_ c параметрами 1, 2: base
                Вызов Class3 приведенного к типу Class2 - _Class3_ c параметрами 1, 2: base

            */

            Console.ReadLine();
        }
    }


Данный пример не очень показателен с точки зрения использования named arguments.
Для этого потребуется несколько аргументов со значениями по умолчанию.

Хотя я рекомендую никогда не использовать значения по умолчанию, вместо этого стоит делать SOLID классы со специализированными методами и понятным API, а named arguments позволяют фривольно писать код в отрыве от порядка аргументов в сигнатуре, хоть задом наперед: c.GetVal( defaultpar: false, B: 42, A: 777);
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы