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

Поскольку множественное наследование - это плохо (оно усложняет исходный код), 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 при изменении одного из интерфейсов.


РЕДАКТИРОВАТЬ

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

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

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

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

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

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

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


person Martin    schedule 07.10.2008    source источник
comment
Если это не просто замаскированное множественное наследование, насколько оно менее сложно?   -  person harpo    schedule 07.10.2008
comment
Если подумать о новых методах расширения в версии 3.5 и о том, как они работают (создание статических вызовов членов), это может быть одним из следующих этапов эволюции языка .NET.   -  person Larry    schedule 07.10.2008
comment
Иногда мне интересно, почему люди просто не делают ... класс A: класс B: класс C?   -  person Chibueze Opata    schedule 25.04.2012
comment
Не позволяйте пропаганде вводить вас в заблуждение. Сам ваш пример показывает, что множественное наследование полезно, а интерфейсы - просто обходной путь для его отсутствия   -  person Kemal Erdogan    schedule 29.09.2013
comment
Я слышал, что C # не выполняет множественное наследование, потому что Java этого не делает, а Java этого не делает, потому что C ++ сделал это таким образом, который имеет некоторые проблемы (при многократном наследовании). Посмотрите, как это делает Эйфель. В отличие от perl, python и некоторых других, но, как и в C ++, порядок наследования не имеет значения (A наследует B и C ≡ A наследует C и B). Эйфель в большинстве случаев разрешает множественное и повторяющееся наследование без драматизма. Однако при конфликте возникает ошибка компиляции. Затем он дает механизмы для разрешения конфликтов.   -  person ctrl-alt-delor    schedule 07.06.2016
comment
если класс FirstAndSecond не наследует интерфейсы IFirst и ISecond, результат будет таким же, зачем делать все эти дополнительные интерфейсные штуки? есть ли у кого-нибудь лучший сценарий для понимания концепции MI с использованием интерфейсов?   -  person Imdad Ali    schedule 25.08.2016
comment
Я уверен, что кто-то уже ответил на это, но композиция - хорошая альтернатива.   -  person byxor    schedule 23.04.2017
comment
Множественное наследование - это неплохо, просто создатели Java были слишком ленивы, чтобы запечь его на своем языке программирования, и эта лень была передана другим, пришедшим позже. C ++ решил проблему множественного наследования задолго до того, как появилась Java.   -  person Carlos Jimenez Bermudez    schedule 04.08.2020


Ответы (14)


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

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

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

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

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

person IanNorton    schedule 08.11.2009
comment
CLR не поддерживает множественное наследование реализаций, только множественное наследование интерфейсов (которое также поддерживается в C #). - person Jordão; 26.07.2010
comment
@ Jordão: Для полноты: компиляторы могут создавать MI для своих типов в CLR. У него есть свои предостережения, например, он не совместим с CLS. Для получения дополнительной информации см. Эту статью (2004 г.) blogs.msdn.com/b/csharpfaq/archive/2004/03/07/ - person dvdvorle; 01.03.2012
comment
Оказывается, вы также можете использовать MI для Python с помощью IronPython - person IanNorton; 02.03.2012
comment
@MrHappy: Очень интересная статья. Я действительно исследовал какой-то способ Композиция признаков для C #, посмотрите. - person Jordão; 03.03.2012
comment
@IanNorton Множественное наследование действительно вызывает сложности. Это также упоминается здесь - blogs.msdn.com/b/csharpfaq/archive/2004/03/07/ - person Mandeep Janjua; 08.05.2012
comment
@IanNorton В 2009 году как вы можете утверждать, что в C # 4.x будет MI? Пожалуйста, перестаньте вводить людей в заблуждение только на основании собственных предположений. - person Mandeep Janjua; 08.05.2012
comment
@MandeepJanjua Я не утверждал ничего подобного, я сказал, что «вполне может представить это». Факт остается фактом: стандарт ECMA CLR действительно предоставляет механизм IL для множественного наследования, просто ничто не использует его в полной мере. - person IanNorton; 09.05.2012
comment
MI существует на C ++, и лично у меня никогда не было проблем с этим - person bzim; 26.02.2017
comment
К вашему сведению, множественное наследование - это неплохо и не усложняет код. Просто подумал, что упомяну об этом. - person Dmitri Nesteruk; 08.04.2017

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

Как только вы это сделаете, вы можете использовать добавленную функцию методов расширения. в 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();
    }
}
person Chris Wenham    schedule 07.10.2008
comment
Но в том-то и дело - такая идея облегчила бы композицию. - person Jon Skeet; 07.10.2008
comment
Не могли бы вы дать ссылку, описывающую концепцию? - person spoulson; 07.10.2008
comment
да, это хорошее использование методов расширения! - person Nic Wise; 07.10.2008
comment
Да, но есть случаи, когда вам действительно нужны методы как часть основного объекта. - person David Pierre; 07.10.2008
comment
К сожалению, данные переменных-членов недоступны в методах расширения, поэтому вы должны предоставлять их как внутренние или (ug) общедоступные, хотя я думаю, что композиция по контракту - лучший способ решить множественное наследование. - person cfeduke; 10.10.2008
comment
Отличный ответ! Лаконичная, понятная, очень полезная иллюстрация. Спасибо! - person AJ.; 21.10.2008
comment
Мы могли бы захотеть проверить, закончил ли myCar поворот влево, прежде чем вызывать Stop. Он может перевернуться, если Stop применяется, когда myCar находится на слишком высокой скорости. : D - person Devraj Gadhavi; 22.06.2015
comment
Отличный ответ. Я знаю, что это более старый пост, но я вижу проблему с составлением специализированного класса, который объединяет несколько полей. Также сложно удовлетворить шаблон IDisposable. Может тема для другого поста. - person Todd; 24.04.2018

Я создал пост-компилятор 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();
person Jordão    schedule 14.05.2011

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

person Joel Coehoorn    schedule 07.10.2008
comment
Это, вероятно, лучшее решение, но не обязательно лучшая идея: p - person leppie; 07.10.2008
comment
разве вам все равно не придется редактировать абстрактный класс при добавлении методов к интерфейсам? - person Rik; 07.10.2008
comment
Рик: Насколько же ты ленив, когда тебе нужно сделать это только один раз? - person leppie; 07.10.2008
comment
@leppie - Каждый раз, когда я добавляю метод к одному из интерфейсов, мне также нужно изменить класс FirstAndSecond. Эта часть исходного вопроса не решается этим решением, не так ли? - person Rik; 07.10.2008
comment
Вам придется редактировать абстрактный класс, но вам НЕ нужно редактировать любые другие классы, которые зависят от него. На этом доллар останавливается, а не продолжает каскадировать ко всей коллекции классов. - person Joel Coehoorn; 07.10.2008
comment
@JoelCoehoorn: Я не слежу за этим ответом ... классы могут реализовывать несколько интерфейсов, так зачем вообще нужен абстрактный базовый класс? Я не могу понять, как это решит проблему MI? - person Hooman Bahreini; 04.12.2020

С 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
}
person Ludmil Tinkov    schedule 13.11.2018
comment
Да, но обратите внимание, что в приведенном выше примере вы не сможете сделать new ConsoleLogger().Log(someEception) - это просто не будет работать, вам придется явно преобразовать свой объект в ILogger, чтобы использовать метод интерфейса по умолчанию. Так что его полезность несколько ограничена. - person Dmitri Nesteruk; 18.11.2019

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

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

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

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

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; }
}
person Paul    schedule 04.04.2017
comment
Но у меня есть и птица, и летучая мышь, которые должны иметь общий код флаера, но явно не принадлежат к одной ветви вашей иерархии. - person Eric G; 15.07.2021

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

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

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

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

person hydrix    schedule 14.12.2017

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

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

person Amy B    schedule 07.10.2008

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

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

person Yogi    schedule 15.01.2014
comment
Я не согласен с первым абзацем. Вы только добавляете в интерфейс подписи тех методов, которые хотите использовать в КАЖДОМ классе. Но вы можете добавить столько дополнительных методов к любым классам, сколько захотите. Кроме того, есть интерфейс извлечения, вызываемый правой кнопкой мыши, что упрощает извлечение интерфейсов. Наконец, ваш пример никоим образом не является наследованием (множественным или иным), однако это отличный пример композиции. Увы, было бы больше достоинств, если бы вы использовали интерфейсы для демонстрации DI / IOC с помощью внедрения конструктора / свойства. Хотя я не буду голосовать против, я не думаю, что это хороший ответ, извините. - person Francis Rodgers; 17.12.2014
comment
Оглядываясь назад на эту ветку через год, я согласен, что вы можете добавить столько методов, сколько хотите в класс, без добавления подписи в интерфейс, однако это сделает интерфейс неполным. Кроме того, мне не удалось найти интерфейс извлечения правой кнопкой мыши в своей среде IDE, возможно, мне что-то не хватало. Но меня больше всего беспокоит то, что когда вы указываете подпись в интерфейсе, наследующие классы должны реализовывать эту подпись. Я считаю, что это двойная работа и может привести к дублированию кодов. - person Yogi; 08.08.2015
comment
ЭКСТРАКТНЫЙ ИНТЕРФЕЙС: щелкните правой кнопкой мыши подпись класса, затем извлеките интерфейс ... в VS2015 тот же процесс за исключением, вы должны щелкнуть правой кнопкой мыши, затем выбрать Quick Actions and Refactorings..., это необходимо знать, это сэкономит вам много времени - person Chef_Code; 26.09.2016

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

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, за исключением новых ivars, которые должны входить в оба. И при использовании такого кода вам не нужно беспокоиться о том, действительно ли ваш объект IGetPerson является IPerson.

person William Jockusch    schedule 20.02.2017

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

person tloach    schedule 07.10.2008
comment
Пожалуйста, не судите MI по C ++, это все равно что судить OOP по PHP или автомобили по Pintos. Эту проблему легко решить: в Eiffel, когда вы наследуете от класса, вам также нужно указать, какие методы вы хотите наследовать, и вы можете их переименовать. Никаких двусмысленностей и никаких сюрпризов. - person Jörg W Mittag; 13.12.2008
comment
@mP: нет, Eiffel обеспечивает истинное множественное наследование реализаций. Переименование не означает потерю цепочки наследования и не приведет к потере возможности кастомизации классов. - person Abel; 19.07.2010

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

  1. Y will provide default functionality for X, so the code for X only has to include stuff which is different from Y.
  2. Almost anyplace a Y would be expected, an X may be used instead.

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

person supercat    schedule 24.08.2011

Я знаю, что знаю, хотя это запрещено и так далее, иногда вам это действительно нужно, поэтому для тех:

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

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

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

person ariel    schedule 28.03.2012
comment
Ваш пример не описывает множественное наследование, так какую проблему вы пытаетесь решить? Настоящий пример множественного наследования будет показывать class a : b, c (реализуя все необходимые дыры в контракте). Возможно, ваши примеры слишком упрощены? - person M.Babcock; 24.01.2013

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

Я основываюсь на подходе IFirst, _2 _, _ 3_, 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
}

C # позволяет вложенным классам получать доступ к защищенным / частным элементам содержащих их классов, поэтому это можно использовать для связывания абстрактных битов из реализации 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 достаточно сложна и количество доступных частных / защищенных методов умеренное, то этот шаблон может помочь преодолеть отсутствие множественного наследования.

person grek40    schedule 12.10.2015