¿Por qué llamar a un método en mi clase derivada llama al método de la clase base?

Considera este código:

class Program { static void Main(string[] args) { Person person = new Teacher(); person.ShowInfo(); Console.ReadLine(); } } public class Person { public void ShowInfo() { Console.WriteLine("I am Person"); } } public class Teacher : Person { public new void ShowInfo() { Console.WriteLine("I am Teacher"); } } 

Cuando ejecuto este código, se da como resultado lo siguiente:

Soy una persona

Sin embargo, puedes ver que es una instancia de Teacher , no de Person . ¿Por qué el código hace eso?

Hay una diferencia entre new / virtual / override .

Se puede imaginar que una clase, cuando se crea una instancia, no es más que una tabla de punteros, que apunta a la implementación real de sus métodos. La siguiente imagen debería visualizar esto bastante bien:

Ilustración de implementaciones de métodos

Ahora hay diferentes formas, se puede definir un método. Cada uno se comporta diferente cuando se usa con herencia. La forma estándar siempre funciona como la imagen de arriba ilustra. Si desea cambiar este comportamiento, puede adjuntar diferentes palabras clave a su método.

1. Clases abstractas

El primero es abstract abstract métodos abstract simplemente apuntan a ninguna parte:

Ilustración de clases abstractas

Si su clase contiene miembros abstractos, también debe marcarse como abstract , de lo contrario, el comstackdor no comstackrá su aplicación. No puede crear instancias de clases abstract , pero puede heredar de ellas y crear instancias de sus clases heredadas y acceder a ellas utilizando la definición de clase base. En su ejemplo, esto se vería así:

 public abstract class Person { public abstract void ShowInfo(); } public class Teacher : Person { public override void ShowInfo() { Console.WriteLine("I am a teacher!"); } } public class Student : Person { public override void ShowInfo() { Console.WriteLine("I am a student!"); } } 

Si se llama, el comportamiento de ShowInfo varía según la implementación:

 Person person = new Teacher(); person.ShowInfo(); // Shows 'I am a teacher!' person = new Student(); person.ShowInfo(); // Shows 'I am a student!' 

Tanto los Student como los Teacher son Person , pero se comportan de manera diferente cuando se les pide que soliciten información sobre ellos mismos. Sin embargo, la forma de pedirles que soliciten su información es la misma: utilizar la interfaz de clase Person .

Entonces, ¿qué sucede detrás de escena, cuando heredas de Person ? Al implementar ShowInfo , el puntero ya no apunta a ninguna parte , ¡ahora apunta a la implementación real! Al crear una instancia de Student , apunta a ShowInfo Student :

Ilustración de métodos heredados

2. Métodos virtuales

La segunda forma es usar métodos virtual . El comportamiento es el mismo, excepto que está proporcionando una implementación predeterminada opcional en su clase base. Las clases con miembros virtual se pueden instanciar, sin embargo, las clases heredadas pueden proporcionar diferentes implementaciones. Así es como debería funcionar realmente tu código para funcionar:

 public class Person { public virtual void ShowInfo() { Console.WriteLine("I am a person!"); } } public class Teacher : Person { public override void ShowInfo() { Console.WriteLine("I am a teacher!"); } } 

La diferencia clave es que el miembro base Person.ShowInfo ya no está apuntando a ninguna parte . Esta es también la razón por la que puede crear instancias de Person (y por lo tanto no es necesario que se marque como abstract ):

Ilustración de un miembro virtual dentro de una clase base

Debería notar que esto no se ve diferente de la primera imagen por ahora. Esto se debe a que el método virtual apunta a una implementación “de la manera estándar “. Al usar virtual , puede decirle a las Persons que pueden (no deben ) proporcionar una implementación diferente para ShowInfo . Si proporciona una implementación diferente (utilizando la override ), como hice para el Teacher anterior, la imagen se vería igual que para el abstract . Imagínese, no proporcionamos una implementación personalizada para Student :

 public class Student : Person { } 

El código se llamaría así:

 Person person = new Teacher(); person.ShowInfo(); // Shows 'I am a teacher!' person = new Student(); person.ShowInfo(); // Shows 'I am a person!' 

Y la imagen para el Student se vería así:

Ilustración de la implementación predeterminada de un método, utilizando virtual-keyword

3. La palabra clave mágica `new` alias” Shadowing ”

new es más un truco alrededor de esto. Puede proporcionar métodos en clases generalizadas, que tienen los mismos nombres que los métodos en la clase / interfaz base. Ambos apuntan a su propia implementación personalizada:

Ilustración del

La implementación se parece a la que proporcionó. El comportamiento difiere según la forma de acceder al método:

 Teacher teacher = new Teacher(); Person person = (Person)teacher; teacher.ShowInfo(); // Prints 'I am a teacher!' person.ShowInfo(); // Prints 'I am a person!' 

Este comportamiento puede ser deseado, pero en su caso es engañoso.

¡Espero que esto aclare las cosas para que entiendan por ti!

El polymorphism de subtipo en C # usa virtualidad explícita, similar a C ++, pero a diferencia de Java. Esto significa que debe marcar explícitamente los métodos como anulables (es decir, virtual ). En C #, también debe marcar explícitamente los métodos de anulación como anulación (es decir, override ) para evitar errores tipográficos.

 public class Person { public virtual void ShowInfo() { Console.WriteLine("I am Person"); } } public class Teacher : Person { public override void ShowInfo() { Console.WriteLine("I am Teacher"); } } 

En el código de su pregunta, usa new , que hace shadowing en lugar de overriding. El remedo simplemente afecta a la semántica de tiempo de comstackción más que a la semántica de tiempo de ejecución, de ahí la salida no intencionada.

Debe hacer que el método sea virtual y debe sobrescribir la función en la clase secundaria, para llamar al método del objeto de clase que coloca en la referencia de clase principal.

 public class Person { public virtual void ShowInfo() { Console.WriteLine("I am Person"); } } public class Teacher : Person { public override void ShowInfo() { Console.WriteLine("I am Teacher"); } } 

Métodos virtuales

Cuando se invoca un método virtual, se comprueba el tipo de tiempo de ejecución del objeto para un miembro que prevalece. Se llama al miembro predominante en la clase más derivada, que podría ser el miembro original, si ninguna clase derivada ha reemplazado al miembro. Por defecto, los métodos no son virtuales. No puede anular un método no virtual. No puede usar el modificador virtual con los modificadores estáticos, abstractos, privados o de anulación, MSDN .

Uso de New for Shadowing

Está utilizando una nueva palabra clave en lugar de anular, esto es lo que hace nuevo

  • Si el método en la clase derivada no está precedido por palabras clave nuevas o anuladas, el comstackdor emitirá una advertencia y el método se comportará como si la palabra clave nueva estuviera presente.

  • Si el método en la clase derivada está precedido por la nueva palabra clave, el método se define como independiente del método en la clase base . Este artículo de MSDN lo explica muy bien.

Enlace temprano VS Enlace final

Tenemos un enlace temprano en tiempo de comstackción para el método normal (no virtual) que es el caso actual que el comstackdor vinculará al método de clase base que es el método de tipo de referencia (clase base) en lugar de que el objeto se mantenga en el refere de base clase, es decir, objeto de clase derivado . Esto es porque ShowInfo no es un método virtual. La vinculación tardía se realiza en tiempo de ejecución para (método virtual / reemplazado) utilizando la tabla de métodos virtuales (vtable).

Para una función normal, el comstackdor puede calcular su ubicación numérica en la memoria. Luego, cuando se llama a la función, puede generar una instrucción para llamar a la función en esta dirección.

Para un objeto que tiene algún método virtual, el comstackdor generará una tabla v. Esto es esencialmente una matriz que contiene las direcciones de los métodos virtuales. Cada objeto que tiene un método virtual contendrá un miembro oculto generado por el comstackdor que es la dirección de la tabla v. Cuando se llama a una función virtual, el comstackdor determinará cuál es la posición del método apropiado en la tabla v. Generará código para buscar en la tabla v de los objetos y llamará al método virtual en esta posición, Referencia .

Quiero construir fuera de la respuesta de Achratt . Para completar, la diferencia es que el OP espera que la new palabra clave en el método de la clase derivada anule el método de la clase base. Lo que realmente hace es ocultar el método de la clase base.

En C #, como se menciona otra respuesta, la anulación de método tradicional debe ser explícita; el método de la clase base debe marcarse como virtual y la clase derivada debe override específicamente el método de la clase base. Si esto se hace, entonces no importa si el objeto se trata como una instancia de la clase base o clase derivada; el método derivado se encuentra y se llama. Esto se hace de manera similar a C ++; un método marcado “virtual” o “anular”, cuando se comstack, se resuelve “tarde” (en tiempo de ejecución) determinando el tipo real del objeto referenciado, y atravesando la jerarquía de objetos hacia abajo a lo largo del árbol desde el tipo de variable al tipo de objeto real, para encontrar la implementación más derivada del método definido por el tipo de variable.

Esto difiere de Java, que permite “invalidaciones implícitas”; por ejemplo, métodos (no estáticos), simplemente definir un método de la misma firma (nombre y número / tipo de parámetros) hará que la subclase anule a la superclase.

Debido a que a menudo es útil ampliar o anular la funcionalidad de un método no virtual que usted no controla, C # también incluye la new palabra clave contextual. La new palabra clave “oculta” el método principal en lugar de anularlo. Cualquier método heredable se puede ocultar ya sea virtual o no; esto le permite a usted, el desarrollador, aprovechar los miembros que desea heredar de un padre, sin tener que trabajar con los que no tiene, mientras le permite presentar la misma “interfaz” a los consumidores de su código.

Ocultar es similar a anular desde la perspectiva de una persona que usa su objeto al nivel de herencia o por debajo del cual se define el método de ocultación. A partir del ejemplo de la pregunta, un codificador que crea un Profesor y almacena esa referencia en una variable del tipo Profesor verá el comportamiento de la implementación de ShowInfo () desde el Profesor, que oculta el de Persona. Sin embargo, alguien que trabaje con su objeto en una colección de registros de Persona (como usted) verá el comportamiento de la implementación de Persona de ShowInfo (); porque el método del profesor no anula a su padre (que también requeriría que Person.ShowInfo () sea virtual), el código que trabaja en el nivel de abstracción de Persona no encontrará la implementación del Profesor y no lo usará.

Además, no solo la new palabra clave hará esto explícitamente, C # permite el ocultamiento de métodos implícitos; simplemente definir un método con la misma firma que un método de clase principal, sin override o new , lo ocultará (aunque generará una advertencia del comstackdor o una queja de ciertos asistentes de refactorización como ReSharper o CodeRush). Este es el compromiso que los diseñadores de C # idearon entre las anulaciones explícitas de C ++ frente a las implícitas de Java, y si bien es elegante, no siempre produce el comportamiento que esperarías si vienes de un fondo en cualquiera de los idiomas más antiguos.

Aquí está lo nuevo: esto se vuelve complejo cuando combina las dos palabras clave en una larga cadena de herencia. Considera lo siguiente:

 class Foo { public virtual void DoFoo() { Console.WriteLine("Foo"); } } class Bar:Foo { public override sealed void DoFoo() { Console.WriteLine("Bar"); } } class Baz:Bar { public virtual void DoFoo() { Console.WriteLine("Baz"); } } class Bai:Baz { public override void DoFoo() { Console.WriteLine("Bai"); } } class Bat:Bai { public new void DoFoo() { Console.WriteLine("Bat"); } } class Bak:Bat { } Foo foo = new Foo(); Bar bar = new Bar(); Baz baz = new Baz(); Bai bai = new Bai(); Bat bat = new Bat(); foo.DoFoo(); bar.DoFoo(); baz.DoFoo(); bai.DoFoo(); bat.DoFoo(); Console.WriteLine("---"); Foo foo2 = bar; Bar bar2 = baz; Baz baz2 = bai; Bai bai2 = bat; Bat bat2 = new Bak(); foo2.DoFoo(); bar2.DoFoo(); baz2.DoFoo(); bai2.DoFoo(); Console.WriteLine("---"); Foo foo3 = bak; Bar bar3 = bak; Baz baz3 = bak; Bai bai3 = bak; Bat bat3 = bak; foo3.DoFoo(); bar3.DoFoo(); baz3.DoFoo(); bai3.DoFoo(); bat3.DoFoo(); 

Salida:

 Foo Bar Baz Bai Bat --- Bar Bar Bai Bai Bat --- Bar Bar Bai Bai Bat 

El primer conjunto de cinco es todo lo esperado; dado que cada nivel tiene una implementación y se hace referencia a él como un objeto del mismo tipo que el que se instancia, el tiempo de ejecución resuelve cada llamada al nivel de herencia al que hace referencia el tipo de variable.

El segundo conjunto de cinco es el resultado de asignar cada instancia a una variable del tipo primario inmediato. Ahora, algunas diferencias en el comportamiento se agitan; foo2 , que en realidad es un molde de Bar como un Foo , aún encontrará el método más derivado del tipo de objeto real Barra. bar2 es un Baz , pero a diferencia de foo2 , porque Baz no anula explícitamente la implementación de Bar (no puede; Bar lo sealed ), no lo ve el tiempo de ejecución cuando mira “de arriba hacia abajo”, por lo que la implementación de Bar se llama . Tenga en cuenta que Baz no tiene que usar la new palabra clave; obtendrá una advertencia del comstackdor si omite la palabra clave, pero el comportamiento implícito en C # es ocultar el método principal. baz2 es un Bai , que anula la new implementación de Baz , por lo que su comportamiento es similar al de foo2 ; se llama la implementación del tipo de objeto real en Bai. bai2 es un Bat , que de nuevo oculta la implementación del método de su padre Bai , y se comporta igual que bar2 aunque la implementación de Bai no está sellada, por lo que teóricamente Bat podría haber reemplazado en lugar de ocultar el método. Finalmente, bat2 es un Bak , que no tiene ninguna implementación primordial de ningún tipo, y simplemente usa el de su padre.

El tercer conjunto de cinco ilustra el comportamiento de resolución completo de arriba hacia abajo. Todo está haciendo referencia a una instancia de la clase más derivada de la cadena, Bak , pero la resolución en cada nivel del tipo de variable se realiza comenzando en ese nivel de la cadena de herencia y profundizando hasta la anulación explícita más derivada del método, que son los de Bar , Bai y Bat . El método de ocultación “rompe” la cadena de herencia primordial; debe trabajar con el objeto al nivel de herencia o por debajo del mismo que oculta el método para que se use el método de ocultación. De lo contrario, el método oculto se “descubre” y se usa en su lugar.

Por favor, lea sobre polymorphism en C #: Polimorfismo (Guía de progtwigción C #)

Este es un ejemplo de allí:

Cuando se utiliza la palabra clave nueva, se llama a los miembros de la nueva clase en lugar de los miembros de la clase base que se han reemplazado. Esos miembros de la clase base se llaman miembros ocultos. Los miembros de clase ocultos aún pueden invocarse si una instancia de la clase derivada se convierte en una instancia de la clase base. Por ejemplo:

 DerivedClass B = new DerivedClass(); B.DoWork(); // Calls the new method. BaseClass A = (BaseClass)B; A.DoWork(); // Calls the old method. 

Necesita hacerlo virtual y luego anular esa función en el Teacher . A medida que va heredando y utilizando el puntero de base para referirse a una clase derivada, debe anularlo usando virtual . new es para ocultar el método de clase base en una referencia de clase derivada y no una referencia de clase base .

Me gustaría agregar un par de ejemplos más para ampliar la información sobre esto. Espero que esto ayude también:

Aquí hay una muestra de código que despeja el air alrededor de lo que sucede cuando un tipo derivado se asigna a un tipo base. Qué métodos están disponibles y la diferencia entre los métodos anulados y ocultos en este contexto.

 namespace TestApp { class Program { static void Main(string[] args) { A a = new A(); a.foo(); // A.foo() a.foo2(); // A.foo2() a = new B(); a.foo(); // B.foo() a.foo2(); // A.foo2() //a.novel() is not available here a = new C(); a.foo(); // C.foo() a.foo2(); // A.foo2() B b1 = (B)a; b1.foo(); // C.foo() b1.foo2(); // B.foo2() b1.novel(); // B.novel() Console.ReadLine(); } } class A { public virtual void foo() { Console.WriteLine("A.foo()"); } public void foo2() { Console.WriteLine("A.foo2()"); } } class B : A { public override void foo() { // This is an override Console.WriteLine("B.foo()"); } public new void foo2() // Using the 'new' keyword doesn't make a difference { Console.WriteLine("B.foo2()"); } public void novel() { Console.WriteLine("B.novel()"); } } class C : B { public override void foo() { Console.WriteLine("C.foo()"); } public new void foo2() { Console.WriteLine("C.foo2()"); } } } 

Otra pequeña anomalía es que, para la siguiente línea de código:

 A a = new B(); a.foo(); 

El comstackdor VS (intellisense) mostraría a.foo () como A.foo ().

Por lo tanto, está claro que cuando se asigna un tipo más derivado a un tipo base, la variable ‘tipo base’ actúa como el tipo base hasta que se hace referencia a un método que se reemplaza en un tipo derivado. Esto puede volverse un poco contra-intuitivo con métodos ocultos o métodos con el mismo nombre (pero no reemplazado) entre los tipos padre e hijo.

¡Este ejemplo de código debería ayudar a delinear estas advertencias!

C # es diferente a Java en el comportamiento de anulación de clase padre / hijo. Por defecto en Java, todos los métodos son virtuales, por lo que el comportamiento que desea se admite de manera predeterminada.

En C # tienes que marcar un método como virtual en la clase base, y luego obtendrás lo que deseas.

La nueva palabra clave indica que el método en la clase actual solo funcionará si tiene una instancia de la clase Teacher almacenada en una variable de tipo Teacher. O puede activarlo utilizando castings: ((Profesor) Persona). ShowInfo ()

El tipo de variable ‘teacher’ aquí es typeof(Person) y este tipo no sabe nada acerca de la clase Teacher y no intenta buscar ningún método en los tipos derivados. Para llamar al método de la clase de profesor, debe convertir su variable: (person as Teacher).ShowInfo() .

Para invocar un método específico basado en el tipo de valor, debe usar la palabra clave ‘virtual’ en su clase base y anular los métodos virtuales en las clases derivadas. Este enfoque permite implementar clases derivadas con o sin anulación de métodos virtuales. Se llamarán los métodos de clase base para los tipos sin superposición de los virtuales.

 public class Program { private static void Main(string[] args) { Person teacher = new Teacher(); teacher.ShowInfo(); Person incognito = new IncognitoPerson (); incognito.ShowInfo(); Console.ReadLine(); } } public class Person { public virtual void ShowInfo() { Console.WriteLine("I am Person"); } } public class Teacher : Person { public override void ShowInfo() { Console.WriteLine("I am Teacher"); } } public class IncognitoPerson : Person { } 

El comstackdor hace esto porque no sabe que es un Teacher . Todo lo que sabe es que es una Person o algo derivado de ella. Entonces todo lo que puede hacer es llamar al método Person.ShowInfo() .

Solo quería dar una breve respuesta –

Debería usar virtual y override en clases que podrían anularse. Use virtual para los métodos que pueden ser reemplazados por clases secundarias y use la override para los métodos que deben anular dichos métodos virtual .

Escribí el mismo código que he mencionado anteriormente en Java, excepto algunos cambios y funcionó bien como excepción. El método de la clase base se anula y, por lo tanto, el resultado que se muestra es “Yo soy profesor”.

Motivo: cuando estamos creando una referencia de la clase base (que es capaz de tener una instancia de referencia de la clase derivada) que en realidad contiene la referencia de la clase derivada. Y como sabemos que la instancia siempre mira primero sus métodos, si la encuentra allí, la ejecuta y, si no encuentra la definición, sube a la jerarquía.

 public class inheritance{ public static void main(String[] args){ Person person = new Teacher(); person.ShowInfo(); } } class Person{ public void ShowInfo(){ System.out.println("I am Person"); } } class Teacher extends Person{ public void ShowInfo(){ System.out.println("I am Teacher"); } } 

Podría ser demasiado tarde … Pero la pregunta es simple y la respuesta debería tener el mismo nivel de complejidad.

En su variable de código, la persona no sabe nada acerca de Teacher.ShowInfo (). No hay forma de llamar al último método desde la referencia de la clase base, porque no es virtual.

Existe un enfoque útil para la herencia: trate de imaginar qué quiere decir con su jerarquía de códigos. También trate de imaginar qué dice una u otra herramienta sobre sí misma. Por ejemplo, si agrega una función virtual a una clase base, supondrá que: 1. puede tener una implementación predeterminada; 2. podría ser reimplementado en clase derivada. Si agrega función abstracta, significa solo una cosa: la subclase debe crear una implementación. Pero en caso de que tenga una función simple, no espera que nadie modifique su implementación.

Basándose en la excelente demostración de Keith S. y en las respuestas de calidad de todos los demás, y por el bien de la totalidad, dejemos que las implementaciones explícitas de interfaces se adelanten para demostrar cómo funciona. Considere lo siguiente:

espacio de nombres LinqConsoleApp {

 class Program { static void Main(string[] args) { Person person = new Teacher(); Console.Write(GetMemberName(() => person) + ": "); person.ShowInfo(); Teacher teacher = new Teacher(); Console.Write(GetMemberName(() => teacher) + ": "); teacher.ShowInfo(); IPerson person1 = new Teacher(); Console.Write(GetMemberName(() => person1) + ": "); person1.ShowInfo(); IPerson person2 = (IPerson)teacher; Console.Write(GetMemberName(() => person2) + ": "); person2.ShowInfo(); Teacher teacher1 = (Teacher)person1; Console.Write(GetMemberName(() => teacher1) + ": "); teacher1.ShowInfo(); Person person4 = new Person(); Console.Write(GetMemberName(() => person4) + ": "); person4.ShowInfo(); IPerson person3 = new Person(); Console.Write(GetMemberName(() => person3) + ": "); person3.ShowInfo(); Console.WriteLine(); Console.ReadLine(); } private static string GetMemberName(Expression> memberExpression) { MemberExpression expressionBody = (MemberExpression)memberExpression.Body; return expressionBody.Member.Name; } } interface IPerson { void ShowInfo(); } public class Person : IPerson { public void ShowInfo() { Console.WriteLine("I am Person == " + this.GetType()); } void IPerson.ShowInfo() { Console.WriteLine("I am interface Person == " + this.GetType()); } } public class Teacher : Person, IPerson { public void ShowInfo() { Console.WriteLine("I am Teacher == " + this.GetType()); } } 

}

Aquí está el resultado:

persona: soy persona == LinqConsoleApp.Teacher

profesor: soy profesor == LinqConsoleApp.Teacher

person1: soy profesor == LinqConsoleApp.Teacher

person2: soy profesor == LinqConsoleApp.Teacher

teacher1: soy profesor == LinqConsoleApp.Teacher

persona4: soy persona == LinqConsoleApp.Person

person3: soy interfaz Person == LinqConsoleApp.Person

Dos cosas a tener en cuenta:
El método Teacher.ShowInfo () omite la nueva palabra clave. Cuando se omite nuevo, el comportamiento del método es el mismo que si la nueva palabra clave se definiera explícitamente.

Solo puede usar la palabra clave override junto con la palabra clave virtual. El método de la clase base debe ser virtual. O abstracto, en cuyo caso la clase también debe ser abstracta.

la persona obtiene la implementación básica de ShowInfo porque la clase Teacher no puede anular la implementación base (sin statement virtual) y la persona es .GetType (Teacher) por lo que oculta la implementación de la clase Teacher.

el docente obtiene la implementación derivada del Profesor de ShowInfo porque el docente es Typeof (Profesor) y no está en el nivel de herencia Person.

person1 obtiene la implementación derivada del Profesor porque es .GetType (Profesor) y la nueva palabra clave implícita oculta la implementación base.

person2 también obtiene la implementación derivada del Profesor a pesar de que implementa IPerson y obtiene un molde explícito para IPerson. Esto es otra vez porque la clase Teacher no implementa explícitamente el método IPerson.ShowInfo ().

teacher1 también obtiene la implementación derivada del Profesor porque es .GetType (Profesor).

Solo person3 obtiene la implementación IPerson de ShowInfo porque solo la clase Person implementa explícitamente el método y person3 es una instancia del tipo IPerson.

Para implementar explícitamente una interfaz, debe declarar una instancia var del tipo de interfaz de destino y una clase debe implementar explícitamente (calificar completamente) los miembros de la interfaz.

Observe que ni siquiera person4 obtiene la implementación de IPerson.ShowInfo. Esto se debe a que aunque person4 es .GetType (Person) y aunque Person implementa IPerson, person4 no es una instancia de IPerson.

Muestra de LinQPad para lanzar a ciegas y reducir la duplicación de código. Creo que es lo que intentabas hacer.

 void Main() { IEngineAction Test1 = new Test1Action(); IEngineAction Test2 = new Test2Action(); Test1.Execute("Test1"); Test2.Execute("Test2"); } public interface IEngineAction { void Execute(string Parameter); } public abstract class EngineAction : IEngineAction { protected abstract void PerformAction(); protected string ForChildren; public void Execute(string Parameter) { // Pretend this method encapsulates a // lot of code you don't want to duplicate ForChildren = Parameter; PerformAction(); } } public class Test1Action : EngineAction { protected override void PerformAction() { ("Performed: " + ForChildren).Dump(); } } public class Test2Action : EngineAction { protected override void PerformAction() { ("Actioned: " + ForChildren).Dump(); } }