Вызов определенного базового метода с несколькими перегрузками и параметрами по умолчанию

#c# #inheritance #overriding #overloading #virtual

#c# #наследование #переопределение #перегрузка #виртуальный

Вопрос:

Я создаю поверх исходного кода игры и пытаюсь вызвать базовую виртуальную функцию с перегруженными методами и параметрами по умолчанию. Я не могу изменить классы, из которых я извлекаю, и мне нужно вызвать функцию внутри моего собственного определения класса виртуального метода. Я попытаюсь объяснить более подробно с помощью кода.

Сначала у нас есть базовый класс A, который определяет виртуальную функцию Foo, которая принимает один аргумент.

 class A
{
   public virtual string Foo(int a)
   {
      return "Class A Function 1 par";
   }
}
  

Затем класс B, который переопределяет Foo и определяет новую перегруженную виртуальную функцию для Foo с двумя новыми параметрами по умолчанию.

 class B : A
{
   public virtual string Foo(int a, int b = 0, int c = 0)
   {
      return "Class B Function 3 par";
   }

   public override string Foo(int a)
   {
      return "Class B Function 1 par";
   }
}
  

Затем класс, из которого мне нужно получить, C. Он просто переопределяет один параметр Foo .

 class C : B
{
   public override string Foo(int a)
   {
      return "Class C Function 1 par";
   }
}
  

Наконец, мой класс D, который также переопределяет один параметр Foo, но также должен иметь возможность вызывать базовый метод Foo.

 class D : C
{
   public override string Foo(int a)
   {
      return base.Foo(0);
   }
}
  

Это приводит к вызову трех параметров Foo, определенных в B (возвращающихся как «Функция класса B 3 номинала»), но я хочу вызвать Foo, определенный в C (возвращал бы «Функцию класса C 1 номинал»). Я бы подумал, что такого рода перегрузка виртуальных функций с параметрами по умолчанию приведет к неоднозначной ошибке компилятора, но она отлично компилируется.

Есть ли способ обойти это, и если нет, то почему это позволяет структуре классов блокировать меня от доступа к базовому методу?

Ответ №1:

Это неудачное взаимодействие между необязательными параметрами и остальной частью языка и веская причина, по которой вы в принципе никогда не должны перегружать методы, которые используют необязательные параметры (или добавлять перегрузки к методам, которые этого не делают). Автор B сделал что-то действительно плохое!

Вызов не является двусмысленным, потому что правила для поиска метода не меняются для base , только правила, для которых метод завершается вызовом после разрешения перегрузки — по сути, перегрузки определяются так, как если бы вызов прочитал ((C) this).Foo(0) . Для этого вызова B.Foo(int, int, int) считается единственным кандидатом, потому что это ближайший не override метод при переходе по цепочке наследования — он выбирается еще до того, как мы приступим к рассмотрению A.Foo(int) . Если бы A был введен метод, проблем бы не было, так как в этом случае перегрузка с одним параметром считалась бы лучшим методом.

Если бы необязательные параметры были частью C # с самого начала, а не были относительно поздним дополнением к партии (с немного причудливой реализацией, где значения расширяются на сайте вызова), это могло бы быть рассмотрено и каким-то образом смягчено. В нынешнем виде наиболее очевидным способом исправить это было бы фактическое изменение правил для base поиска, чтобы он предпочитал сопоставлять методы, которые точно соответствуют сигнатуре метода, в котором он встречается, но это только еще больше усложнило бы и без того сложные правила разрешения перегрузки, и это, безусловно, может нарушить существующий код, поэтому шансы на то, что что-то подобное произойдет, невелики.

Если вы не можете изменить A , B и C все еще есть способ написать D , чтобы получить желаемое поведение, используя (если это слово) другую довольно неясную функцию C #, которая еще старше: группы методов!

 class D : C
{
   public override string Foo(int a) 
   {
      Func<int, string> foo = base.Foo;
      return foo(a);
   }
}    
  

Это однозначно вызывает C.Foo(int) , потому что преобразования делегатов в группах методов не учитывают необязательные параметры и, следовательно, B.Foo(int, int, int) не является допустимым кандидатом, заставляя нас идти дальше по цепочке и находить A.Foo(int) .