Множественное наследование в C # - PullRequest
202 голосов
/ 07 октября 2008

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

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

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 .

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

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

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

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

Чтобы не было необходимости обновлять класс FirstAndSecond при изменении одного из интерфейсов.


EDIT

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

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

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

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

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

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

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

Ответы [ 16 ]

203 голосов
/ 07 октября 2008

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

Как только вы это сделаете, вы можете использовать функцию Extension Methods , добавленную в C # 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();
    }
}
116 голосов
/ 08 ноября 2009

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

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

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

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

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

14 голосов
/ 07 октября 2008

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

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

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

13 голосов
/ 14 мая 2011

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

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

Вы можете запустить посткомпилятор как событие пост-сборки 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 голосов
/ 07 октября 2008

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

3 голосов
/ 20 октября 2008

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

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

1 голос
/ 15 января 2014

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

Следуя шаблону проектирования Фасада, мы можем имитировать наследование от нескольких классов, используя методы доступа . Объявите классы как свойства с {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 будет иметь доступ ко всем методам и свойствам Class Father и Mother, имитирующим множественное наследование, наследующим экземпляр родительских классов. Не совсем то же самое, но это практично.

1 голос
/ 07 октября 2008

Если вы можете жить с тем ограничением, что методы 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();
}

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

0 голосов
/ 13 ноября 2018

С C # 8 теперь вы практически имеете множественное наследование через реализацию интерфейса по умолчанию:

interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}

class ConsoleLogger : ILogger
{
    public void Log(LogLevel level, string message) { ... }
    // Log(Exception) gets default implementation
}
0 голосов
/ 15 декабря 2017

В моей собственной реализации я обнаружил, что использование классов / интерфейсов для MI, хотя и «в хорошей форме», имеет тенденцию вызывать чрезмерные сложности, поскольку вам нужно настроить все это множественное наследование только для нескольких необходимых вызовов функций, и в В моем случае это нужно было сделать буквально в десятки раз излишне.

Вместо этого было проще сделать статические «функции, которые вызывают функции, которые вызывают функции» в различных модульных разновидностях, как своего рода замена ООП. Решением, над которым я работал, была «система заклинаний» для RPG, в которой эффекты должны сильно вызывать функцию смешивания и сопоставления, чтобы дать огромное разнообразие заклинаний без переписывания кода, как в примере кажется, указывает.

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

Кодирование кажется намного быстрее и чище, таким образом, IMO. Если вы просто делаете функции и вам не нужны унаследованные свойства , используйте функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...