Множественное наследование в 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 forms.
насколько я знаю, сейчас у вас есть два способа сделать это:
написать новый класс, который наследуется от компонентов и реализует интерфейс класса TextTcpClient с помощью экземпляра класса сам как показано с FirstAndSecond.
напишите новый класс, который наследует от TextTcpClient и каким-то образом реализует IComponent (на самом деле еще не пробовал это).
в обоих случаях вам нужно выполнять работу по методу, а не по классу. Поскольку вы знаете, что нам понадобятся все методы TextTcpClient и Component, было бы самым простым решением просто объединить эти два в один класс.
чтобы избежать конфликтов, это может быть сделано по генерации кода, где результат может быть изменен впоследствии, но набрав это вручную, это чистая боль в заднице.
15 ответов:
поскольку множественное наследование плохо (это делает источник более сложным), C# не предоставляет такой шаблон напрямую. Но иногда было бы полезно иметь эту способность.
C# и .NET CLR не реализовали MI, потому что они не пришли к выводу, как он будет взаимодействовать между C#, VB.net и другие языки еще не потому, что "это сделало бы источник более сложным"
Ми является полезной концепцией, ООН ответил на вопросы одни как:- "что вы делаете, когда у вас есть несколько общих базовых классов в разных суперклассов?
Perl-это единственный язык, с которым я когда-либо работал, где MI работает и работает хорошо. .Net вполне может представить его однажды, но еще нет, CLR уже поддерживает MI, но, как я уже сказал, для него пока нет языковых конструкций.
до тех пор вы застряли с прокси-объектами и несколькими интерфейсами вместо этого : (
рассмотреть только с помощью состав вместо того, чтобы пытаться имитировать множественное наследование. Вы можете использовать интерфейсы, чтобы определить, какие классы составляют композиции, например:
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(); } }
Я бы тоже этого хотел - это то, что я лично называю миксом, хотя я понимаю, что это перегруженный термин. Я хотел бы иметь возможность указать переменную, используемую для реализации интерфейса, с возможностью предоставить свою собственную реализацию для конкретных методов.
Я блог об этом более подробно - хотя в контексте намеренного завышения того, что это может означать с точки зрения наследования.
Я не вижу причин, почему этого не может быть реализован в компиляторе C# - но это еще одна сложность языка...
Я создал C# post-compiler что позволяет такого рода вещи:
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 post-build-event:
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();
У вас может быть один абстрактный базовый класс, который реализует как IFirst, так и ISecond, а затем наследуется только от этой базы.
MI не плохо, все, кто (серьезно) использовал его, любят его, и это не усложняет код! По крайней мере, не больше, чем другие конструкции могут усложнить код. Плохой код-это плохой код, независимо от того, есть ли MI на картинке.
в любом случае, у меня есть милое маленькое решение для множественного наследования, которое я хотел поделиться, это at;http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog или вы можете перейти по ссылке в моем sig... :)
Если вы можете жить с ограничением, что методы 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(); }
Да использование интерфейса является хлопот, потому что каждый раз, когда мы добавляем метод в классе мы должны добавить подпись в интерфейсе. Кроме того, что если у нас уже есть класс с кучей методов, но нет интерфейса для этого? мы должны вручную создать интерфейс для всех классов, которые мы хотим наследовать. И самое худшее, мы должны реализовать все методы в интерфейсах в дочернем классе, если дочерний класс должен наследовать от множественного интерфейса.
по следующим Шаблон дизайна фасада мы можем имитировать наследование от нескольких классов с помощью аксессоры. Объявите классы как свойства с {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 будет иметь доступ ко всем методам и свойствам класса Father и Mother, имитация множественного наследования, наследование экземпляра родительских классов. Не совсем то же самое, но это практично.
множественное наследование-это одна из тех вещей, что обычно вызывает больше проблем, чем решает. В C++ это соответствует шаблону предоставления вам достаточно веревки, чтобы повеситься, но Java и C# выбрали более безопасный маршрут, не давая вам возможности. Самая большая проблема заключается в том, что делать, если вы наследуете несколько классов, которые имеют метод с той же сигнатурой, что наследник не реализует. Какой метод класса он должен выбрать? Или это не должно компилироваться? Есть вообще еще один способ реализовать большинство вещей, которые не зависят от множественного наследования.
Если X наследует от Y, это имеет два несколько ортогональных эффекта:
- Y обеспечит функциональность по умолчанию для X, поэтому код для X должен включать только то, что отличается от Y.
- почти в любом месте можно было бы ожидать Y, вместо этого можно использовать X.
хотя наследование обеспечивает обе функции, нетрудно представить себе обстоятельства, при которых одна из них может быть полезна без другой. Ни один язык .net, о котором я знаю, не имеет прямой способ реализации первого без второго, хотя можно было бы получить такую функциональность, определив базовый класс, который никогда не используется напрямую, и имея один или несколько классов, которые наследуют непосредственно от него, не добавляя ничего нового (такие классы могли бы совместно использовать весь свой код, но не были бы заменяемыми друг для друга). Однако любой CLR-совместимый язык позволит использовать интерфейсы, которые обеспечивают вторую функцию интерфейсов (заменяемость) без первого (член повторное использование.)
Я знаю, я знаю несмотря на то, что это не разрешено и так далее, когда-то вам действительно нужно это так для тех:
class a {} class b : a {} class c : b {}
как в моем случае я хотел сделать это класс b: форма (да, окна.формы) класс c: b {}
причина половина функции были идентичны и с интерфейсом u должны переписать их все
я строю на
IFirst
,ISecond
,First
,Second
,FirstAndSecond
подход, как это было представлено в вопросе. Я уменьшаю пример кода доIFirst
, так как шаблон остается неизменным независимо от количества интерфейсов / 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 достаточно сложна, а количество доступных частных / защищенных методов является умеренным, то этот шаблон может помочь преодолеть отсутствие нескольких наследование.
Это соответствует ответу Лоуренса Уэнхема, но в зависимости от вашего варианта использования это может быть или не быть улучшением-вам не нужны сеттеры.
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 (). С этого момента в основном весь код человека входит в IGetPerson, а не в IPerson, кроме новых ivars, которые должны входить в оба. И в использовании таких код, вам не нужно беспокоиться о том, является ли ваш объект IGetPerson на самом деле IPerson.
мы все, кажется, направляемся вниз по интерфейсному пути с этим, но очевидная другая возможность, здесь, заключается в том, чтобы сделать то, что должен делать ООП, и построить свое дерево наследования... (разве это не то, что дизайн класса все о?)
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; } }
в моей собственной реализации я обнаружил, что использование классов/интерфейсов для MI, хотя и "хорошая форма", как правило, является массивным усложнением, поскольку вам нужно настроить все это множественное наследование только для нескольких необходимых вызовов функций, и в моем случае это нужно было сделать буквально в десятки раз избыточно.
вместо этого было проще просто сделать статические "функции, которые вызывают функции, которые вызывают функции" в разных модульных разновидностях в качестве своего рода замены ООП. Этот решение, над которым я работал, было "системой заклинаний" для RPG, где эффекты должны сильно mix-and-match функция вызова, чтобы дать крайнее разнообразие заклинаний без переписывания кода, так же, как пример, кажется, указывает.
большинство функций теперь могут быть статическими, потому что мне не обязательно нужен экземпляр для логики заклинаний, тогда как наследование классов не может даже использовать виртуальные или абстрактные ключевые слова во время статики. Интерфейсы не могут использовать их на все.
кодирование кажется намного быстрее и чище таким образом ИМО. Если вы просто выполняете функции, и вам не нужно унаследованное свойства использование функций.