Herencia Múltiple en C#


Dado que la herencia múltiple es mala (hace que la fuente sea más complicada) C# no proporciona dicho patrón directamente. Pero a veces sería útil tener esta habilidad.

Por ejemplo, soy capaz de implementar el patrón de herencia múltiple que falta usando interfaces y tres clases como esa:

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class First:IFirst 
{ 
    public void FirstMethod() { Console.WriteLine("First"); } 
}

public class Second:ISecond 
{ 
    public void SecondMethod() { Console.WriteLine("Second"); } 
}

public class FirstAndSecond: IFirst, ISecond
{
    First first = new First();
    Second second = new Second();
    public void FirstMethod() { first.FirstMethod(); }
    public void SecondMethod() { second.SecondMethod(); }
}

Cada vez que agrego un método a una de las interfaces necesito cambiar la clase FirstAndSecond también.

¿Hay una manera de inyectar múltiples clases existentes en una nueva clase como es posible en C++?

Tal vez hay una solución utilizando algún tipo de generación de código?

O puede verse así (sintaxis imaginaria de c#):

public class FirstAndSecond: IFirst from First, ISecond from Second
{ }

Para que no haya necesidad de actualizar la clase FirstAndSecond cuando modifique una de las interfaces.


EDITAR

Tal vez sería mejor considerar un ejemplo práctico:

Tiene una clase existente (por ejemplo, un cliente TCP basado en texto basado en ITextTcpClient) que ya utiliza en diferentes ubicaciones dentro de su proyecto. Ahora siente la necesidad de crear un componente de su clase para que sea de fácil acceso para los desarrolladores de Windows forms.

Por lo que sé, actualmente tienes dos maneras de hacer esto:

  1. Escriba una nueva clase que se hereda de componentes e implemente la interfaz de la clase TextTcpClient usando una instancia de la propia clase como se muestra con FirstAndSecond.

  2. Escribir un nuevo clase que hereda de TextTcpClient y de alguna manera implementa IComponent (todavía no lo he probado).

En ambos casos es necesario trabajar por método y no por clase. Ya que sabe que necesitaremos todos los métodos de TextTcpClient y Component, sería la solución más fácil combinar esos dos en una clase.

Para evitar conflictos, esto se puede hacer mediante la generación de código donde el resultado podría alterarse después, pero escribir esto a mano es un dolor en el culo.

Author: JK., 2008-10-07

15 answers

Dado que la herencia múltiple es mala (hace que la fuente sea más complicada) C# no proporciona dicho patrón directamente. Pero a veces sería útil tener esta habilidad.

C# y .net CLR no han implementado MI porque no han concluido cómo se inter-operar entre C#, VB.net y los otros idiomas sin embargo, no porque "sería el origen más complejo"

MI es un concepto útil, las preguntas sin respuesta son unas como: - " ¿Qué haces cuando tienes múltiples clases base comunes en las diferentes superclases?

Perl es el único lenguaje con el que he trabajado donde MI funciona y funciona bien. . Net bien puede introducirlo algún día, pero aún no, el CLR ya admite MI, pero como he dicho, no hay construcciones de lenguaje para él más allá de eso todavía.

Hasta entonces usted está atascado con objetos Proxy y múltiples interfaces en su lugar: (

 103
Author: IanNorton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-05 23:24:33

Considere simplemente usar composición en lugar de intentar simular la Herencia Múltiple. Puede usar Interfaces para definir qué clases componen la composición, por ejemplo: ISteerable implica una propiedad de tipo SteeringWheel, IBrakable implica una propiedad de tipo BrakePedal, etc.

Una vez que haya hecho eso, podría usar la característica Extension Methods añadida a C # 3.0 para simplificar aún más los métodos de llamada en esas propiedades implícitas, por ejemplo:

public interface ISteerable { SteeringWheel wheel { get; set; } }

public interface IBrakable { BrakePedal brake { get; set; } }

public class Vehicle : ISteerable, IBrakable
{
    public SteeringWheel wheel { get; set; }

    public BrakePedal brake { get; set; }

    public Vehicle() { wheel = new SteeringWheel(); brake = new BrakePedal(); }
}

public static class SteeringExtensions
{
    public static void SteerLeft(this ISteerable vehicle)
    {
        vehicle.wheel.SteerLeft();
    }
}

public static class BrakeExtensions
{
    public static void Stop(this IBrakable vehicle)
    {
        vehicle.brake.ApplyUntilStop();
    }
}


public class Main
{
    Vehicle myCar = new Vehicle();

    public void main()
    {
        myCar.SteerLeft();
        myCar.Stop();
    }
}
 197
Author: Chris Wenham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-06 21:37:02

Me gustaría esto también - es lo que personalmente me refiero como una mezcla, aunque me doy cuenta de que es un término sobrecargado. Me gustaría poder especificar la variable utilizada para implementar la interfaz, con la opción de proporcionar mi propia implementación para métodos específicos.

He blogueado sobre esto con más detalle - aunque en el contexto de una deliberada exageración de lo que podría significar en términos de herencia.

No veo ninguna razón por la que esto no pueda implementarse en la C# compilador - pero es otro poco de complejidad del lenguaje...

 14
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-30 09:39:14

He creado un Post-compilador de C# que habilita este tipo de cosas:

using NRoles;

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class RFirst : IFirst, Role {
  public void FirstMethod() { Console.WriteLine("First"); }
}

public class RSecond : ISecond, Role {
  public void SecondMethod() { Console.WriteLine("Second"); }
}

public class FirstAndSecond : Does<RFirst>, Does<RSecond> { }

Puede ejecutar el post-compilador como un evento post-build de Visual Studio:

C:\some_path\nroles-v0.1.0-bin\nutate.exe " Targ (TargetPath)"

En el mismo ensamblaje lo usas así:

var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();

En otra asamblea se usa así:

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();
 11
Author: Jordão,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-03 16:42:27

Puede tener una clase base abstracta que implemente tanto iFirst como ISecond, y luego herede solo de esa base.

 4
Author: Joel Coehoorn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-07 13:09:28

MI NO está mal, todo el mundo que lo ha usado (en serio) lo ama y no complica el código! Al menos no más que otras construcciones pueden complicar el código. Código malo es código malo, independientemente de si MI está o no en la imagen.

De todos modos, tengo una pequeña solución para la Herencia Múltiple que quería compartir, está en; http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog o puedes seguir el enlace en mi sig... :)

 3
Author: Thomas Hansen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-20 19:55:28

Si puede vivir con la restricción de que los métodos de iFirst e ISecond solo deben interactuar con el contrato de iFirst e ISecond (como en su ejemplo)... puede hacer lo que pida con métodos de extensión. En la práctica, esto rara vez es el caso.

public interface IFirst {}
public interface ISecond {}

public class FirstAndSecond : IFirst, ISecond
{
}

public static MultipleInheritenceExtensions
{
  public static void First(this IFirst theFirst)
  {
    Console.WriteLine("First");
  }

  public static void Second(this ISecond theSecond)
  {
    Console.WriteLine("Second");
  }
}

///

public void Test()
{
  FirstAndSecond fas = new FirstAndSecond();
  fas.First();
  fas.Second();
}

Así que la idea básica es definir la implementación requerida en las interfaces... este material requerido debe apoyar la implementación flexible en los métodos de extensión. En cualquier momento que necesite " añadir métodos a la interfaz " en lugar de agregar un método de extensión.

 1
Author: Amy B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-07 13:23:19

Sí usar Interfaz es una molestia porque cada vez que agregamos un método en la clase tenemos que agregar la firma en la interfaz. Además, ¿qué pasa si ya tenemos una clase con un montón de métodos pero sin interfaz para ella? tenemos que crear manualmente la Interfaz para todas las clases que queremos heredar. Y lo peor es, tenemos que implementar todos los métodos en las interfaces en la clase hijo si la clase hijo es heredar de la interfaz múltiple.

Siguiendo el diseño de la fachada patrón podemos simular heredar de múltiples clases usando accessors. Declare las clases como propiedades con {get; set;} dentro de la clase que necesita heredar y todas las propiedades y métodos públicos son de esa clase, y en el constructor de la clase hija instancie las clases padre.

Por ejemplo:

 namespace OOP
 {
     class Program
     {
         static void Main(string[] args)
         {
             Child somechild = new Child();
             somechild.DoHomeWork();
             somechild.CheckingAround();
             Console.ReadLine();
         }
     }

     public class Father 
     {
         public Father() { }
         public void Work()
         {
             Console.WriteLine("working...");
         }
         public void Moonlight()
         {
             Console.WriteLine("moonlighting...");
         }
     }


     public class Mother 
     {
         public Mother() { }
         public void Cook()
         {
             Console.WriteLine("cooking...");
         }
         public void Clean()
         {
             Console.WriteLine("cleaning...");
         }
     }


     public class Child 
     {
         public Father MyFather { get; set; }
         public Mother MyMother { get; set; }

         public Child()
         {
             MyFather = new Father();
             MyMother = new Mother();
         }

         public void GoToSchool()
         {
             Console.WriteLine("go to school...");
         }
         public void DoHomeWork()
         {
             Console.WriteLine("doing homework...");
         }
         public void CheckingAround()
         {
             MyFather.Work();
             MyMother.Cook();
         }
     }


 }

Con esta estructura, el Hijo de la clase tendrá acceso a todos los métodos y propiedades de la Clase Padre y Madre, simulando herencia múltiple, heredando una instancia de las clases principales. No es exactamente lo mismo, pero es práctico.

 1
Author: Yogi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-22 15:58:57

La herencia múltiple es una de esas cosas que generalmente causa más problemas de los que resuelve. En C++ se ajusta al patrón de darle suficiente cuerda para colgarse, pero Java y C# han optado por ir por la ruta más segura de no darle la opción. El mayor problema es qué hacer si hereda varias clases que tienen un método con la misma firma que el heredero no implementa. ¿Qué método de clase debe elegir? ¿O no debería eso compilar? Hay generalmente otra forma de implementar la mayoría de las cosas que no dependen de la herencia múltiple.

 0
Author: tloach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-07 13:31:55

Si X hereda de Y, eso tiene dos efectos algo ortogonales:

  1. Y proporcionará la funcionalidad predeterminada para X, por lo que el código para X solo tiene que incluir cosas que sean diferentes de Y.
  2. Casi en cualquier lugar se esperaría una Y, se puede usar una X en su lugar.

Aunque la herencia proporciona ambas características, no es difícil imaginar circunstancias en las que cualquiera de las dos podría ser útil sin la otra. Ningún lenguaje. net que conozco tiene una forma directa de implementar el primero sin la segunda, aunque se podría obtener tal funcionalidad definiendo una clase base que nunca se usa directamente, y teniendo una o más clases que heredan directamente de ella sin agregar nada nuevo (tales clases podrían compartir todo su código, pero no serían sustituibles entre sí). Sin embargo, cualquier lenguaje compatible con CLR permitirá el uso de interfaces que proporcionen la segunda característica de las interfaces (sustituibilidad) sin la primera (reutilización de miembros).

 0
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-24 14:40:29

Sé que sé a pesar de que no está permitido y así sucesivamente, en algún momento u realmente lo necesitan para los:

class a {}
class b : a {}
class c : b {}

, Como en mi caso, quería hacer esto clase b: Forma (sí, las ventanas.forma) clase c: b {}

Causa la mitad de la función eran idénticos y con interfaz u debe reescribirlos todos

 0
Author: ariel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-28 11:35:53

Dado que la cuestión de la herencia múltiple (MI) aparece de vez en cuando, me gustaría agregar un enfoque que aborda algunos problemas con el patrón de composición.

Construyo sobre la IFirst, ISecond,First, Second, FirstAndSecond enfoque, tal como se presenta en la pregunta. Reduzco el código de muestra a IFirst, ya que el patrón permanece igual independientemente del número de interfaces / clases base MI.

Supongamos que con MI First y Second ambos derivarían de la misma base class BaseClass, usando solo elementos de interfaz pública de BaseClass

Esto se puede expresar añadiendo una referencia de contenedor a BaseClass en la implementación First y Second:

class First : IFirst {
  private BaseClass ContainerInstance;
  First(BaseClass container) { ContainerInstance = container; }
  public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); } 
}
...

Las cosas se vuelven más complicadas, cuando se hace referencia a elementos de interfaz protegidos de BaseClass o cuando First y Second serían clases abstractas en MI, requiriendo que sus subclases implementen algunas partes abstractas.

class BaseClass {
  protected void DoStuff();
}

abstract class First : IFirst {
  public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
  protected abstract void DoStuff(); // base class reference in MI
  protected abstract void DoSubClassStuff(); // sub class responsibility
}

C # permite a las clases anidadas acceder a elementos protegidos / privados de sus clases contenedoras, así que esto puede ser usado para enlazar los bits abstractos de la implementación First.

class FirstAndSecond : BaseClass, IFirst, ISecond {
  // link interface
  private class PartFirst : First {
    private FirstAndSecond ContainerInstance;
    public PartFirst(FirstAndSecond container) {
      ContainerInstance = container;
    }
    // forwarded references to emulate access as it would be with MI
    protected override void DoStuff() { ContainerInstance.DoStuff(); }
    protected override void DoSubClassStuff() { ContainerInstance.DoSubClassStuff(); }
  }
  private IFirst partFirstInstance; // composition object
  public FirstMethod() { partFirstInstance.FirstMethod(); } // forwarded implementation
  public FirstAndSecond() {
    partFirstInstance = new PartFirst(this); // composition in constructor
  }
  // same stuff for Second
  //...
  // implementation of DoSubClassStuff
  private void DoSubClassStuff() { Console.WriteLine("Private method accessed"); }
}

Hay bastantes repeticiones involucradas, pero si la implementación real de FirstMethod y SecondMethod son suficientemente complejas y la cantidad de métodos privados/protegidos a los que se accede es moderada, entonces este patrón puede ayudar a superar la falta de herencia múltiple.

 0
Author: grek40,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-12 13:04:18

Esto está en la línea de la respuesta de Lawrence Wenham, pero dependiendo de su caso de uso, puede o no ser una mejora you no necesita los configuradores.

public interface IPerson {
  int GetAge();
  string GetName();
}

public interface IGetPerson {
  IPerson GetPerson();
}

public static class IGetPersonAdditions {
  public static int GetAgeViaPerson(this IGetPerson getPerson) { // I prefer to have the "ViaPerson" in the name in case the object has another Age property.
    IPerson person = getPerson.GetPersion();
    return person.GetAge();
  }
  public static string GetNameViaPerson(this IGetPerson getPerson) {
    return getPerson.GetPerson().GetName();
  }
}

public class Person: IPerson, IGetPerson {
  private int Age {get;set;}
  private string Name {get;set;}
  public IPerson GetPerson() {
    return this;
  }
  public int GetAge() {  return Age; }
  public string GetName() { return Name; }
}

Ahora cualquier objeto que sepa cómo obtener a una persona puede implementar IGetPerson, y automáticamente tendrá los métodos Getageviapperson() y Getnameviapperson (). A partir de este punto, básicamente todo el código de Persona entra en IGetPerson, no en IPerson, aparte de los nuevos ivars, que tienen que entrar en ambos. Y al usar dicho código, usted no tiene que preocuparse por si su objeto IGetPerson es en sí mismo en realidad un IPerson.

 0
Author: William Jockusch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-20 10:20:59

Todos parece que estamos yendo por el camino de la interfaz con esto, pero la otra posibilidad obvia, aquí, es hacer lo que OOP se supone que debe hacer, y construir su árbol de herencia... (¿no es esto de lo que se trata el diseño de clases?)

class Program
{
    static void Main(string[] args)
    {
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

Esta estructura proporciona bloques de código reutilizables y, seguramente, ¿cómo se debe escribir el código OOP?

Si este enfoque en particular no se ajusta a la factura, simplemente creamos nuevas clases basadas en los objetos requeridos...

class Program
{
    static void Main(string[] args)
    {
        fish shark = new fish();
        shark.size = "large";
        shark.lfType = "Fish";
        shark.name = "Jaws";
        Console.WriteLine(shark.name);
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

public class aquatic : lifeform
{
    public string size { get; set; }
}

public class fish : aquatic
{
    public string name { get; set; }
}
 0
Author: Paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-04 10:13:35

En mi propia implementación encontré que el uso de clases/interfaces para MI, aunque "buena forma", tendía a ser una complicación excesiva masiva ya que necesita configurar toda esa herencia múltiple para solo unas pocas llamadas a funciones necesarias, y en mi caso, necesitaba hacerse literalmente docenas de veces redundantemente.

En cambio, era más fácil hacer simplemente "funciones estáticas que llaman a funciones que llaman a funciones" en diferentes variedades modulares como una especie de reemplazo de OOP. La solución que estaba estaba trabajando en el "sistema de hechizos" para un RPG donde los efectos necesitan en gran medida mezclar y emparejar llamadas a funciones para dar una variedad extrema de hechizos sin reescribir el código, al igual que el ejemplo parece indicar.

La mayoría de las funciones ahora pueden ser estáticas porque no necesariamente necesito una instancia para la lógica ortográfica, mientras que la herencia de clases ni siquiera puede usar palabras clave virtuales o abstractas mientras esté estática. Las interfaces no pueden usarlas en absoluto.

La codificación parece mucho más rápida y limpia por aquí IMO. Si solo está haciendo funciones, y no necesita propiedades heredadas , use funciones.

 0
Author: hydrix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-14 23:26:26