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