Множественное наследование в С#

Поскольку множественное наследование плохо (это делает источник более сложным), С# не предоставляет такой шаблон напрямую. Но иногда было бы полезно иметь эту способность.

Например, я могу реализовать отсутствующий шаблон множественного наследования с использованием интерфейсов и трех классов:

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(); }
}

Каждый раз, когда я добавляю метод к одному из интерфейсов, мне нужно также изменить класс FirstAndSecond.

Есть ли способ внедрить несколько существующих классов в один новый класс, как это возможно в С++?

Может быть, есть решение, использующее какое-то генерирование кода?

Или это может выглядеть так (воображаемый синтаксис С#):

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

Таким образом, при изменении одного из интерфейсов не потребуется обновлять класс FirstAndSecond.


ИЗМЕНИТЬ

Может быть, лучше рассмотреть практический пример:

У вас есть существующий класс (например, текстовый TCP-клиент на основе ITextTcpClient), который вы уже используете в разных местах внутри вашего проекта. Теперь вы чувствуете потребность создать компонент своего класса, чтобы он был легко доступен для разработчиков форм Windows.

Насколько я знаю, у вас есть два способа сделать это:

  • Напишите новый класс, который наследуется от компонентов и реализует интерфейс класса TextTcpClient, используя экземпляр самого класса, как показано в FirstAndSecond.

  • Напишите новый класс, который наследует от TextTcpClient и каким-то образом реализует IComponent (на самом деле он еще не пытался это сделать).

В обоих случаях вам нужно выполнить работу за метод, а не за класс. Поскольку вы знаете, что нам понадобятся все методы TextTcpClient и Component, это было бы самым простым решением просто объединить эти два в один класс.

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

Ответ 1

Поскольку множественное наследование плохо (это делает источник более сложным), С# не предоставляет такой шаблон напрямую. Но иногда было бы полезно иметь эту способность.

С# и .net CLR не реализовали MI, потому что они не пришли к выводу, как он будет взаимодействовать между С#, VB.net и другими языками, но не потому, что "это сделает источник более сложным"

MI - полезная концепция, ответы на непогашенные вопросы: "Что вы делаете, когда у вас есть несколько общих базовых классов в разных суперклассах?

Perl - единственный язык, с которым я когда-либо работал, где MI работает и работает хорошо..Net может хорошо представить его однажды, но еще нет, CLR уже поддерживает MI, но, как я уже сказал, для него еще нет языковых конструкций.

До тех пор вы застряли с объектами Proxy и несколькими интерфейсами: (

Ответ 2

Подумайте, просто используя composition вместо того, чтобы пытаться имитировать множественное наследование. Вы можете использовать интерфейсы для определения того, какие классы составляют состав, например: ISteerable подразумевает свойство типа SteeringWheel, IBrakable означает свойство типа BrakePedal и т.д.

Как только вы это сделаете, вы можете использовать функцию Extension Methods, добавленную в С# 3.0, чтобы еще больше упростить методы вызова для этих подразумеваемых свойств, например:

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();
    }
}

Ответ 3

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

Я писал об этом более подробно - хотя в контексте преднамеренного завышения того, что это может означать с точки зрения наследования.

Я не вижу причин, почему это невозможно реализовать в компиляторе С#, но это еще одна сложность языка...

Ответ 4

Я создал пост-компилятор С#, который позволяет делать такие вещи:

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> { }

Вы можете запустить посткомпилятор как событие post-build-события Visual Studio:

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

В той же сборке вы используете ее так:

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

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

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();

Ответ 5

У вас может быть один абстрактный базовый класс, который реализует как IFirst, так и ISecond, а затем наследует только от этой базы.

Ответ 6

МИ не плох, все, кто (серьезно) использовал его, ЛЮБИТ это, и это НЕ УТВЕРЖДАЕТ код! По крайней мере, не больше, чем другие конструкции могут усложнить код. Плохой код - это плохой код, независимо от того, находится ли MI на изображении.

Во всяком случае, у меня есть небольшое небольшое решение для множественного наследования, которое я хотел бы разделить, он на; http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog, или вы можете перейти по ссылке в моем sig...:)

Ответ 7

Если вы можете жить с ограничением того, что методы IFirst и ISecond должны взаимодействовать только с контрактом IFirst и ISecond (как в вашем примере)... вы можете делать то, что вы просите с помощью методов расширения. На практике это редко бывает.

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();
}

Итак, основная идея заключается в том, что вы определяете требуемую реализацию в интерфейсах... этот необходимый материал должен поддерживать гибкую реализацию в методах расширения. Каждый раз, когда вам нужно "добавлять методы к интерфейсу", вместо этого вы добавляете метод расширения.

Ответ 8

Да использование интерфейса - это проблема, потому что, когда мы добавляем метод в класс, мы должны добавить подпись в интерфейс. Кроме того, что, если у нас уже есть класс с кучей методов, но нет интерфейса для него? мы должны вручную создать интерфейс для всех классов, которые мы хотим унаследовать. И самое страшное, мы должны реализовать все методы в интерфейсах в дочернем классе, если дочерний класс должен наследовать от множественного интерфейса.

Следуя шаблону проектирования фасадов, мы можем имитировать наследование из нескольких классов с помощью accessors. Объявите классы как свойства с {get; set;} внутри класса, которые должны наследовать, и все общедоступные свойства и методы принадлежат к этому классу, а в конструкторе дочернего класса создаются экземпляры родительских классов.

Например:

 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();
         }
     }


 }

с этим классом структуры Child будет иметь доступ ко всем методам и свойствам Отца и Матери класса, имитируя множественное наследование, наследуя экземпляр родительских классов. Не совсем то же самое, но это практично.

Ответ 9

Множественное наследование является одной из тех вещей, которые обычно вызывают больше проблем, чем решает. В С++ он соответствует шаблону предоставления вам достаточно веревки, чтобы повесить себя, но Java и С# решили использовать более безопасный маршрут, не предоставляя вам вариант. Самая большая проблема заключается в том, что делать, если вы наследуете несколько классов, у которых есть метод с той же сигнатурой, что и inheritee. Какой метод класса выбрать? Или это не скомпилировать? Существует, как правило, другой способ реализовать большинство вещей, которые не зависят от множественного наследования.

Ответ 10

Если X наследует от Y, который имеет два несколько ортогональных эффекта:

  • Y предоставит функциональность по умолчанию для X, поэтому код для X должен включать только материал, отличный от Y.
  • Почти везде, где Y ожидалось, вместо X можно использовать X.

Хотя наследование обеспечивает обе функции, нетрудно представить себе обстоятельства, при которых любой может быть полезен без другого. Нет. Язык, который я знаю, имеет прямой способ реализовать первый без второго, хотя можно получить такую ​​функциональность, определив базовый класс, который никогда не используется напрямую, и имеющий один или несколько классов, которые наследуют непосредственно от него, не добавляя ничего новые (такие классы могли бы использовать весь свой код, но не были бы взаимозаменяемы друг для друга). Однако любой язык, совместимый с CLR, позволит использовать интерфейсы, которые обеспечивают вторую функцию интерфейсов (замену) без первого (повторное использование членов).

Ответ 11

Я знаю, что знаю даже если это не разрешено и т.д., иногда и так нужно для них:

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

как в моем случае я хотел сделать это   class b: Форма (yep windows.forms)   класс c: b {}

причина, по которой половина функции была идентичной, и интерфейс u должен переписать их все

Ответ 12

Поскольку вопрос о множественном наследовании (MI) появляется время от времени, я хотел бы добавить подход, который решает некоторые проблемы с шаблоном композиции.

Я основываюсь на подходах IFirst, ISecond, First, Second, FirstAndSecond, как это было представлено в вопросе. Я сокращаю пример кода до IFirst, так как шаблон остается неизменным независимо от количества базовых классов интерфейсов /MI.

Предположим, что с MI First и Second оба будут получены из одного и того же базового класса BaseClass, используя только элементы открытого интерфейса из BaseClass

Это можно выразить, добавив ссылку на контейнер для BaseClass в реализацию First и Second:

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

Все сложнее, когда ссылки на элементы защищенного интерфейса из BaseClass или когда First и Second будут абстрактными классами в MI, требуя, чтобы их подклассы реализовали некоторые абстрактные части.

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
}

С# позволяет вложенным классам обращаться к защищенным/закрытым элементам их содержащих классов, поэтому это можно использовать для связывания абстрактных битов от реализации 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"); }
}

Существует довольно некоторый шаблонный подход, но если фактическая реализация FirstMethod и SecondMethod достаточно сложна, а объем доступа к закрытым/защищенным методам является умеренным, то этот шаблон может помочь преодолеть нехватку множественного наследования.

Ответ 13

Множественное наследование может быть достигнуто в С# с использованием интерфейсов. Его можно объяснить с помощью примера -

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MultipleInheritApplication
{

    interface calc1
    {
        int add(int a, int b);
    }

    interface calc2
    {
        int sub(int x, int y);
    }
    interface calc3
    {
        int mul(int r, int s);
    }
    interface calc4
    {
        int div(int c, int d);
    }
    class Calculation : calc1, calc2, calc3, calc4
    {
        public int result1;
        public int add(int a, int b)
        {
            return result1 = a + b;
        }
        public int result2;
        public int sub(int x, int y)
        {
            return result2 = x - y;
        }
        public int result3;
        public int mul(int r, int s)
        {
            return result3 = r * s;
        }
        public int result4;
        public int div(int c, int d)
        {
            return result4 = c / d;
        }
         class Program
        {
            static void Main(string[] args)
            {
                Calculation c = new Calculation();
                c.add(8, 2);
                c.sub(20, 10);
                c.mul(5, 2);
                c.div(20, 10);
                Console.WriteLine("Multiple Inheritance concept Using Interfaces :\n ");
                Console.WriteLine("Addition: " + c.result1);
                Console.WriteLine("Substraction: " + c.result2);
                Console.WriteLine("Multiplication :" + c.result3);
                Console.WriteLine("Division: " + c.result4);
                Console.ReadKey();
            }
        }
    }
}

Ответ 14

Это в соответствии с ответом Лоуренса Уэнам, но в зависимости от вашего варианта использования это может быть или не быть улучшением - вам не нужны сеттеры.

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; }
}

Теперь любой объект, который знает, как получить человека, может реализовать IGetPerson, и он автоматически будет иметь методы GetAgeViaPerson() и GetNameViaPerson(). С этого момента в основном весь код Person переходит в IGetPerson, а не в IPerson, кроме новых иваров, которые должны идти в обоих. И при использовании такого кода вам не нужно беспокоиться о том, действительно ли ваш объект IGetPerson на самом деле является IPerson.

Ответ 15

Мы все, похоже, движемся по пути интерфейса с этим, но очевидная другая возможность здесь состоит в том, чтобы делать то, что должен делать ООП, и создать свое дерево наследования... (не это ли какой класс дизайн все о?)

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; }
}

Эта структура предоставляет многократно используемые блоки кода и, конечно же, как писать OOP-код?

Если этот конкретный подход не совсем подходит для законопроекта, мы просто создаем новые классы на основе требуемых объектов...

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; }
}