Учитывая следующие определения классов C# и код:
public class BaseClass
{
public virtual void MyMethod()
{
...do something...
}
}
public class A : BaseClass
{
public override void MyMethod()
{
...do something different...
}
}
public class B : BaseClass
{
public override void MyMethod()
{
...do something different...
}
}
public class AnotherObject
{
public AnotherObject(BaseClass someObject)
{
someObject.MyMethod(); //This calls the BaseClass method, unfortunately.
}
}
Я хотел бы назвать MyMethod (), который на самом деле найден в A или B, предположив, что объект передал в, на самом деле экземпляр A или B, не того, что найдено в BaseClass. За исключением выполнения чего-то вроде этого:
public class AnotherObject
{
public AnotherObject(BaseClass someObject)
{
A temp1 = someObject as A;
if (A != null)
{
A.MyMethod();
}
B temp2 = someObject as B;
if (B != null)
{
B.MyMethod();
}
}
}
Как я могу сделать это?
Какой метод будет вызван, определяется через полиморфизм типа, передаваемого в конструктор AnotherObject:
AnotherObject a = new AnotherObject(new A()); // invokes A.MyMethod()
AnotherObject b = new AnotherObject(new B()); // invokes B.MyMethod()
AnotherObject c = new AnotherObject(new BaseClass()); //invokes BaseClass.MyMethod()
Извините, но вы совершенно ошибаетесь; это противоречит всему смыслу виртуальных методов. Если someObject
является A
, то будет вызван A.MyMethod
. Если someObject
является B
, то будет вызван B.MyMethod
. Если someObject
является BaseClass
, а не экземпляром типа, производного от BaseClass
, то будет вызван BaseClass.MyMethod
.
Используем всеми любимый пример:
class Animal {
public virtual void Speak() {
Console.WriteLine("i can haz cheezburger?");
}
}
class Feeder {
public void Feed(Animal animal) { animal.Speak(); }
}
class Cat : Animal {
public override void Speak() { Console.WriteLine("Meow!"); }
}
class Dog : Animal {
public override void Speak() { Console.WriteLine("Woof!"); }
}
Тогда:
Animal a = new Animal();
Animal c = new Cat();
Animal d = new Dog();
Feeder f = new Feeder();
f.Feed(a);
f.Feed(c);
f.Feed(d);
Это выведет:
i can haz cheezburger?
Meow!
Woof!
Опять же, в этом вся суть виртуальных методов.
Далее, мы можем обратиться к спецификации. Из 10.6.3 (Виртуальные методы)
При вызове виртуального метода, тип времени выполнения экземпляра, для которого происходит вызов, определяет фактическую реализацию метода для вызова.
(Выделение жирным и курсив в оригинале. )
В точных терминах, когда метод с именем
N
вызывается со списком аргументовA
на экземпляре с типом времени компиляцииC
и типом времени выполненияR
(гдеR
- это либоC
, либо класс, производный отC
), вызов обрабатывается следующим образом:- Во-первых, разрешение перегрузки применяется к
C
,N
иA
, чтобы выбрать конкретный методM
из набора методов, объявленных вC
и унаследованных от него. Это описано в §7.5.5.1.- Затем, если
M
является невиртуальным методом, вызываетсяM
.- Иначе,
M
является виртуальным методом, и вызывается наиболее производная реализацияM
относительно R.
(Выделение жирным в оригинале.)
Тогда нам нужно определение "наиболее производной реализации M
". Это хорошее рекурсивное определение:
Наиболее производная реализация виртуального метода
M
относительно классаR
определяется следующим образом:- Если
R
содержит вводящее виртуальное объявлениеM
, то это наиболее производная реализацияM
.- Иначе, если
R
содержит переопределениеM
, то это самая производная реализацияM
.- Иначе, наиболее производная реализация
M
относительноR
такая же, как наиболее производная реализацияM
относительно прямого базового классаR
.
Таким образом, в нашем примере выше с Cat : Animal
и Dog : Animal
, когда параметр a
в Feeder.Feed(Animal)
является экземпляром Cat
, то Cat.Speak
является наиболее производной реализацией. Вот почему мы увидим "Мяу!
", а не "i can haz cheezburger?
"
Если MyMethod ()
является абстрактным для базового класса, то будет использоваться версия в производных классах. Поэтому, если вам не нужно вызывать экземпляр в базовом классе, это будет вариант.
static void Main(string[] args)
{
A classA = new A();
B classB = new B();
DoFunctionInClass(classA);
DoFunctionInClass(classB);
DoFunctionInClass(classA as BaseClass);
Console.ReadKey();
}
public static void DoFunctionInClass(BaseClass c)
{
c.MyMethod();
}
public abstract class BaseClass
{
public abstract void MyMethod();
}
public class A : BaseClass
{
public override void MyMethod()
{
Console.WriteLine("Class A");
}
}
public class B : BaseClass
{
public override void MyMethod()
{
Console.WriteLine("Class B");
}
}
Если переданный someObject является классом A, то вызывается A.MyMethod, а не реализация базового класса. Также обратите внимание на ключевое слово is.
Поскольку вы ввели его как BaseClass, а не A или B, базовый класс является начальной точкой для вызовов методов.
Вы можете попробовать использовать generic:
public class AnotherObject
{
public AnotherObject<T>(T someObject) where T : BaseClass
{
someObject.MyMethod(); //This calls the BaseClass method, unfortunately.
}
}
Я не уверен, насколько хорошо это будет работать в конструкторе, но вы можете перенести это в другой метод.