Я встречал множество аргументов против включения множественного наследования в C#, некоторые из которых включают (помимо философских аргументов):
Я вырос в C++ и скучаю по мощи и элегантности множественного наследования. Хотя он не подходит для всех программных разработок, бывают ситуации, когда трудно отрицать его полезность по сравнению с интерфейсами, композицией и подобными объектно-ориентированными методами.
Означает ли исключение множественного наследования, что разработчики недостаточно умны, чтобы использовать их с умом, и неспособны решать возникающие сложности?
Я лично приветствовал бы введение множественного наследования в C# (возможно, C##).
Дополнение: Мне было бы интересно узнать из ответов, кто исходит из одного (или процедурного фона), а не из фона множественного наследования. Я часто обнаруживал, что разработчики, не имеющие опыта работы с множественным наследованием, часто по умолчанию используют аргумент «множественное наследование не является необходимым» просто потому, что у них нет опыта работы с этой парадигмой.





Я ни разу не пропустил этого, никогда. Да, это [MI] становится сложным, и да, интерфейсы выполняют аналогичную работу во многих отношениях, но это не самое главное: в общем смысле в большинстве случаев это просто не нужно. Во многих случаях злоупотребляют даже одинарным наследованием.
Я только что проголосовал, так что считайте это +1
Что ж, всегда можно вернуться через несколько часов ;-p
Я слышал аналогичные аргументы от разработчиков, имеющих опыт процедурного программирования. Их аргумент был бы «объекты, никогда не нуждающиеся в них». Это упускает суть. Если они более выразительны, они имеют ценность (в правильной ситуации).
Строго говоря (и я здесь просто перефразирую Страуструпа), это правда, и также верно, что единичное наследование на самом деле не нужно. Кроме того, классы тоже не нужны, правда? Речь идет не о том, что «нужно», а о том, что «приятно иметь».
Бывают ситуации, когда поможет любая отдельная функция. Означает ли это, что язык должен быть кухонной раковиной со всеми возможными функциями, о которых вы только можете подумать? Я не верю, что дополнительная сложность множественного наследования того стоит.
Если бы вы уносили предметы, мне было бы очень их не хватать; в этом различие. Объекты добавляют огромную ценность при минимальной сложности. Множественное наследование добавляет (возможно) минимальную ценность при огромной сложности.
Но объекты на самом деле представляют собой огромную сложность по сравнению с отсутствием объектов. Как и существование наследования, интерфейсов или любой другой продвинутой концепции.
Хорошо, позвольте мне перефразировать; с моей зашоренной точки зрения как разработчика .NET / работа с объектами / проста. Я знаю, что очень быстро потерплю неудачу с МИ. Но да, точка взята - объект увеличения сложности. Речь идет о сложности и вознаграждении; объекты имеют большую выгоду; У МИ есть некоторая выгода, но ИМО недостаточно.
Объекты - это огромная сложность со соразмерной отдачей. Однокорневое наследование работало более десяти лет на Java, около 20 лет - на Obj-C ... Дизайнеры C# пытались предотвратить случайные ошибки, следовали традиционному дизайну и (ИМХО) поступили правильно. Состав> Наследование!
Марк, вы писали код с использованием множественного наследования? В нем определенно есть сложность, но это не какой-то ужасный неукротимый зверь, каким его часто изображают.
Но есть и дикая сторона ...
MI прост, но если вы не можете доверять себе, чтобы не создавать массивную древовидную иерархию, то я думаю, вам вообще не следует программировать. Я думаю, что разработчик C# просто хотел облегчить себе жизнь, MI сложен для компиляторов.
Лично я предпочел бы найти дизайн, который не нуждался бы во мне (простом смертном) в понимать массивной древовидной иерархии.
@Marc: Проблема с MI не в странных деревьях, проблема в ромбах / петлях.
@tloach: правда - но я использовал эту фразу как прямую цитату из gbjbaanb
потом нужно перечитать. Я имел в виду людей, которые создают огромные деревья, а затем говорят: «МИ - это сложно». Если бы они использовали это разумно, у них не было бы таких проблем, которые сами себе причиняли. То же самое относится ко всему, включая объекты.
@gbjbaanb - достаточно честно; это могло быть истолковано любым способом, и я выбрал неправильный; Я понимаю вашу точку зрения и полностью согласен! Извините за недопонимание...
Я не пропустил «стереотипное» множественное наследование, но пропустил его в том виде, в котором оно используется в шаблоне «Любопытно повторяющийся шаблон» (en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern) .
Вы, вероятно, не пропустили это, потому что ваш разум твердо привязан к образу мыслей о единственном наследовании ... Я лично нахожу ему множество применений.
В большинстве случаев он не нужен, но это не значит, что в некоторых крошечных случаях он вам не нужен.
@Marc, это может быть немного больше, чем несколько часов, но +1 только что приземлился. Лучше поздно, чем никогда, а? знак равно
@Rob - смеется; почти 2 года; р
+1 Я согласен с тем, что нам не нужен MI, так как мы в основном хотим, чтобы он улучшал повторное использование кода. Но я действительно думаю, что нам нужно что-то, может быть, лучший способ составить наши классы. Как с чертами или роли.
Я думаю, что это слишком усложнит ситуацию без достаточной рентабельности инвестиций. Мы уже видим, как люди режут .NET-код слишком глубокими деревьями наследования. Я могу только представить себе зверства, если бы люди обладали способностью передавать множественное наследование.
Не стану отрицать, что у него есть потенциал, но я просто не вижу достаточной пользы.
Вы утверждаете, что у компилятора есть жесткие ограничения на глубину наследования классов?
C# поддерживает одиночное наследование, интерфейсы и методы расширения. Вместе они предоставляют практически все, что предоставляет множественное наследование, без головной боли, которую приносит множественное наследование.
(кроме, возможно, состояния от нескольких предков ... но я с вами в этом ;-p)
@Marc, черт возьми, я знал, что мои радикальные обобщения вернутся, чтобы укусить меня. Немного отредактировано, чтобы охватить вашу точку зрения :)
Похоже, но не то же самое. Я также утверждаю, что методы расширения обладают собственным вкусом сложности. Я думаю, что у CodingHorror был пост по этому поводу.
Сказал Coding Horror post, всем, кому интересно.
Я бы возражал против множественного наследования просто по той причине, о которой вы говорите. Разработчики будут злоупотреблять этим. Я видел достаточно проблем с каждым классом, наследуемым от служебного класса, просто чтобы вы могли вызывать функцию из каждого класса, не набирая так много, чтобы знать, что множественное наследование во многих ситуациях приведет к плохому коду. То же самое можно сказать и о GoTo, что является одной из причин, по которой его использование так неодобрительно. Я думаю, что множественное наследование имеет несколько хороших применений, как и GoTo. В идеальном мире, где они оба использовались только при правильном подходе, не было бы проблем. Однако мир не идеален, поэтому мы должны защищать плохих программистов от самих себя.
Microsoft сделала аналогичные предположения, когда представила VB. Они предположили, что разработчики недостаточно умны, чтобы разрабатывать хороший код, поэтому язык был соответственно упрощен. Я лично нашел это оскорблением и отказался от VB в пользу более выразительных языков.
Однако теперь VB.Net расширился до такой степени, что в нем есть все функции C#.
Искажение языка для обеспечения безопасности разработчиков - не лучший принцип дизайна. Это проблема Java (ничего, кроме классов и интерфейсов, поэтому ее легко понять ...).
Прочтите книгу Страуструпа о дизайне и эволюции. У него бескомпромиссная позиция по этому поводу: ничто не будет исключено из языка только потому, что он будет использоваться неправильно. Он стремился к грамотному программисту (и, честно говоря, я все равно не хочу, чтобы некомпетентные писали программы).
Никто не хочет, чтобы «некомпетентные люди пишут программы» ... К сожалению, в реальном мире многие «некомпетентные» делают это, и когда они уйдут или будут уволены, а вы застрянете на их шедеврах, я уверен, вы будете рады этим «некомпетентным» по крайней мере, у меня не было МИ ...
Один из худших ответов, которые я когда-либо встречал!
Хотя, безусловно, есть примеры, когда это может быть полезно, я обнаружил, что в большинстве случаев, когда я думаю, что мне это нужно, на самом деле это не так.
Я работаю с C# с тех пор, как он был впервые доступен как альфа / бета-версия, и никогда не пропускал множественное наследование. MI хорош для некоторых вещей, но почти всегда есть другие способы достижения того же результата (некоторые из которых в конечном итоге оказываются проще или создают более понятную реализацию).
Не уверен, что согласен. Бывают ситуации, когда MI дает гораздо более элегантные решения, чем это возможно с помощью любых альтернативных методов, обсуждаемых в этой ветке. Его нужно использовать в правильной ситуации, и им можно злоупотреблять, но опять же, как и любой из инструментов в наборе инструментов C# (методы расширения?)
Предпочитайте агрегирование наследованию!
class foo : bar, baz
часто лучше справляется с
class foo : Ibarrable, Ibazzable
{
...
public Bar TheBar{ set }
public Baz TheBaz{ set }
public void BarFunction()
{
TheBar.doSomething();
}
public Thing BazFunction( object param )
{
return TheBaz.doSomethingComplex(param);
}
}
Таким образом, вы можете менять местами различные реализации IBarrable и IBazzable для создания нескольких версий приложения без необходимости писать еще один класс.
Внедрение зависимостей может в этом сильно помочь.
Я согласен. Возможности делегирования и композиции значительно превосходят возможности множественного наследования. И зачастую это в любом случае более устойчивое решение.
Вы можете использовать миксин здесь вместо того, чтобы писать композицию вручную :-)
Абсолютно. Однако я пытался проиллюстрировать концепцию, что часто лучше всего делать, демонстрируя наивную реализацию.
Замените в вашем примере «агрегирование» на «состав». Если вы пришли из множественного наследования, вам НУЖНЫ все классы в вашем составном суперклассе. Вот различия между aggregation и composition: en.wikipedia.org/wiki/Object_composition#Aggregation
Да, вы можете это сделать, но это больно! Если язык поддерживает MI - это торт.
Множественное наследование в целом может быть полезным, и многие языки объектно-ориентированного программирования так или иначе реализуют его (C++, Eiffel, CLOS, Python ...). Это важно? Нет. Это приятно иметь? да.
То же самое можно сказать о любой функции языка программирования. Сравните C# с ассемблером - все функции C# не являются существенными, просто ДЕЙСТВИТЕЛЬНО приятно иметь.
Правда. Приятно иметь все возможности языка программирования.
Обновлять
Я призываю всех, кто проголосует против меня, показать мне любой пример множественного наследования, которое я не могу легко перенести на язык с единичным наследованием. Если кто-то не может показать такой образец, я утверждаю, что его не существует. Я перенес тонны кода C++ (MH) на Java (без MH), и это никогда не было проблемой, независимо от того, сколько MH использовалось в коде C++.
До сих пор никто не мог доказать, что множественное наследование имеет любое преимущество по сравнению с другими методами, которые вы упомянули в своем сообщении (используя интерфейсы и делегаты, я могу получить точно такой же результат без большого количества кода или накладных расходов), хотя у него есть пара хорошо известных недостатков (проблема с алмазом является самые раздражающие).
На самом деле множественным наследованием обычно злоупотребляют. Если вы используете объектно-ориентированный дизайн, чтобы каким-то образом смоделировать реальный мир в классы, вы никогда не дойдете до точки, когда множественное наследование действительно имеет смысл. Можете ли вы привести полезный пример множественного наследования? Большинство примеров, которые я видел до сих пор, на самом деле «неправильные». Они делают что-то подклассом, который на самом деле является просто дополнительным свойством и, следовательно, интерфейсом.
Взгляните на Sather. Это язык программирования, где интерфейсы имеют множественное наследование, почему бы и нет (это не может создать проблему с ромбами), однако классы, которые не являются интерфейсами, не имеют никакого наследования. Они могут реализовывать только интерфейсы, и они могут «включать» другие объекты, что делает эти другие объекты их фиксированной частью, но это не то же самое, что наследование, это скорее форма делегирования (вызовы методов, «унаследованные» включением объектов, являются фактически просто пересылается экземплярам этих объектов, инкапсулированных в ваш объект). Я думаю, что эта концепция довольно интересна и показывает, что у вас может быть полностью чистый объектно-ориентированный язык без какого-либо наследования реализации.
Я согласен с тем, что у него есть свои сложности (проблема с алмазом является одной из них), но невозможно полностью выразить множественное наследование с помощью интерфейсов, методов композиции или расширений (или какой-либо их комбинации).
Возможно, вам понадобится только иногда написать немного больше кода, но функциональность абсолютно одинакова. Я портировал тонны приложений C++ на Java, в C++ есть MH, а в Java нет, никогда не было проблем с портированием любого из них, у меня те же классы и те же интерфейсы (и только несколько дополнительных классов)
Вопрос не в том, чтобы заставить его работать, это вопрос в получении желаемого кода. Я привел пример, в котором мне мог бы помочь MI (я хочу добавить методы к выпущенным интерфейсам, которые не могут быть изменены - это легко сделать с абстрактными классами, но только при наличии MI).
@Lou: Для этого вам не нужен МИ. Хорошо, я думаю, что Java не может этого сделать, но Objective-C может это сделать (мы регулярно расширяем собственные классы Apple собственными методами), а Objective-C не имеет MI.
Коллега написал в этом блоге о том, как получить что-то вроде множественного наследования в C# с помощью динамической компиляции:
Нет.
(для голосования)
Я думаю, что это действительно просто. Как и любую другую сложную парадигму программирования, вы можете злоупотребить ею и навредить себе. Можете ли вы неправильно использовать объекты (о да!), Но это не значит, что объектно-ориентированный объект плох сам по себе.
Аналогично с МИ. Если у вас нет большого «дерева» унаследованных классов или большого количества классов, которые предоставляют методы с одинаковыми именами, тогда вам будет хорошо с MI. Фактически, поскольку MI предоставляет реализацию, вам часто будет лучше, чем реализация SI, где вам придется перекодировать или вырезать и вставить методы в делегированные объекты. В этих случаях лучше меньше кода. Вы можете создать беспорядок, разделяя код, пытаясь повторно использовать объекты через наследование интерфейса. И такие обходные пути плохо пахнут.
Я считаю, что модель .NET с единичным наследованием ошибочна: они должны были использовать только интерфейсы или только MI. Наличие «половины и половины» (т.е. наследования одной реализации плюс наследование множественных интерфейсов) сбивает с толку больше, чем должно быть, и совсем не элегантно.
Я происхожу из MI, и меня это не пугает и не обжигает.
Я размещал это здесь пару раз, но я просто думаю, что это действительно круто. Вы можете узнать, как подделать MI здесь. Я также думаю, что в статье подчеркивается, почему МИ - это такая боль, даже если это не было запланировано.
Я не скучаю по нему и не нуждаюсь в этом, я предпочитаю использовать композицию объектов для достижения своих целей. В этом и суть статьи.
В статье упоминается ровно одна проблема с МИ: ромбовидный узор. Чтобы избежать такой возможности, подробно рассматриваются довольно сложные обходные пути для реализации MI. Я не впечатлен рассуждениями: не было бы проще просто иметь МИ и говорить «НЕ ДЕЛАЙ АЛМАЗ!»?
Вот очень полезный случай множественного наследования, с которым я постоянно сталкиваюсь.
Как поставщик инструментария я не могу изменять опубликованные API, иначе я нарушу обратную совместимость. Одна вещь, которая возникает из этого, заключается в том, что я никогда не смогу добавить к интерфейсу после того, как я его выпустил, потому что это нарушит компиляцию для любого, кто его реализует - единственный вариант - расширить интерфейс.
Это нормально для существующих клиентов, но новые будут рассматривать эту иерархию как излишне сложную, и если бы я проектировал ее с самого начала, я бы не стал реализовывать ее таким образом - я должен, иначе я потеряю обратную совместимость. . Если интерфейс внутренний, то просто добавляю и исправляю разработчиками.
Во многих случаях новый метод интерфейса имеет очевидную и небольшую реализацию по умолчанию, но я не могу ее предоставить.
Я бы предпочел использовать абстрактные классы, а затем, когда мне нужно добавить метод, добавить виртуальный с реализацией по умолчанию, и иногда мы делаем это.
Проблема, конечно, в том, что если этот класс, вероятно, будет смешан с чем-то, что уже что-то расширяет, тогда у нас нет выбора, кроме как использовать интерфейс и иметь дело с интерфейсами расширения.
Если мы думаем, что у нас есть эта проблема, мы вместо этого выбираем богатую модель событий - что, я думаю, вероятно, правильный ответ в C#, но не все проблемы решаются таким образом - иногда вам нужен простой общедоступный интерфейс , и более богатый для расширителей.
Я сам использовал множественное наследование в C++, но вам действительно нужно знать, что вы делаете, чтобы не попасть в беду, особенно если у вас есть два базовых класса, у которых есть общий дедушка или бабушка. Тогда вы можете столкнуться с проблемами виртуального наследования, когда вам придется объявлять каждый конструктор, который вы собираетесь вызывать по цепочке (что значительно усложняет повторное использование двоичного кода) ... это может будет беспорядком.
Что еще более важно, способ построения CLI в настоящее время не позволяет реализовать MI с легкостью. Я уверен, что они могли бы это сделать, если бы захотели, но у меня есть другие вещи, которые я бы предпочел увидеть в CLI, чем множественное наследование.
Вещи, которые я хотел бы видеть, включают некоторые функции Спецификация #, например ссылочные типы, не допускающие значения NULL. Я также хотел бы видеть больше безопасности объекта, имея возможность объявлять параметры как const и возможность объявлять функцию const (это означает, что вы гарантируете, что внутреннее состояние объекта не будет изменено методом и компилятор дважды проверяет вас).
Я думаю, что между одиночным наследованием, множественным наследованием интерфейсов, обобщениями и методами расширения вы можете делать практически все, что вам нужно. Если что-то может улучшить ситуацию для кого-то, кто желает MI, я думаю, нужна какая-то языковая конструкция, которая позволила бы упростить агрегирование и композицию. Таким образом, вы можете иметь общий интерфейс, но затем делегировать свою реализацию частному экземпляру класса, от которого вы обычно наследуете. Прямо сейчас для этого нужно много кода. Наличие более автоматизированной языковой функции для этого значительно поможет.
Множественное наследование не поддерживается CLR каким-либо образом, о котором я знаю, поэтому я сомневаюсь, что его можно поддерживать эффективным способом, как в C++ (или Eiffel, что может сделать это лучше, учитывая, что язык специально разработан. для меня).
Хорошая альтернатива множественному наследованию - Traits. Это позволяет вам смешивать различные единицы поведения в один класс. Компилятор может поддерживать признаки как расширение времени компиляции для системы типов с единичным наследованием. Вы просто объявляете, что класс X включает черты A, B и C, а компилятор объединяет запрошенные вами черты вместе, чтобы сформировать реализацию X.
Например, предположим, что вы пытаетесь реализовать IList (of T). Если вы посмотрите на разные реализации IList (of T), они часто используют один и тот же код. Вот и появились черты. Вы просто объявляете черту с общим кодом в ней, и вы можете использовать этот общий код в любой реализации IList (из T) - даже если в реализации уже есть какой-то другой базовый класс. Вот как может выглядеть синтаксис:
/// This trait declares default methods of IList<T>
public trait DefaultListMethods<T> : IList<T>
{
// Methods without bodies must be implemented by another
// trait or by the class
public void Insert(int index, T item);
public void RemoveAt(int index);
public T this[int index] { get; set; }
public int Count { get; }
public int IndexOf(T item)
{
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
for (int i = 0; i < Count; i++)
if (comparer.Equals(this[i], item))
return i;
return -1;
}
public void Add(T item)
{
Insert(Count, item);
}
public void Clear()
{ // Note: the class would be allowed to override the trait
// with a better implementation, or select an
// implementation from a different trait.
for (int i = Count - 1; i >= 0; i--)
RemoveAt(i);
}
public bool Contains(T item)
{
return IndexOf(item) != -1;
}
public void CopyTo(T[] array, int arrayIndex)
{
foreach (T item in this)
array[arrayIndex++] = item;
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
int i = IndexOf(item);
if (i == -1)
return false;
RemoveAt(i);
return true;
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < Count; i++)
yield return this[i];
}
}
И вы используете эту черту так:
class MyList<T> : MyBaseClass, DefaultListMethods<T>
{
public void Insert(int index, T item) { ... }
public void RemoveAt(int index) { ... }
public T this[int index] {
get { ... }
set { ... }
}
public int Count {
get { ... }
}
}
Конечно, я здесь лишь поверхностно. Для более полного описания см. Статью Черты: составные единицы поведения (PDF).
В языке Rust (от Mozilla) трейты реализованы интересным образом: они заметили, что трейты похожи на реализации интерфейса по умолчанию, поэтому они объединили интерфейсы и трейты в одну функцию (которую они называют трейтами). Основное различие между типажами и реализациями интерфейса по умолчанию (которые сейчас есть в Java) состоит в том, что трейты могут содержать частные или защищенные методы, в отличие от традиционных методов интерфейса, которые должны быть общедоступными. Если черты и интерфейсы объединены в одну функцию нет, то еще одно отличие состоит в том, что вы можете иметь ссылку на интерфейс, но не можете иметь ссылку на черту; черта сама по себе не является типом.
Интересное предложение. Чем это отличается от использования вспомогательного класса или агрегации?
Как вы могли бы реализовать IList с агрегацией или вспомогательным классом (при условии, что у вас уже есть базовый класс, который не реализует IList)? Без трейтов вам все равно придется реализовать все методы IList. Кроме того, у черт есть и другие преимущества - вам придется читать статью.
Я также думаю, что черты были бы отличным дополнением к C# и полностью заменили бы необходимость множественного наследования. Уже есть исследования по добавлению черт в C#: scg.unibe.ch/archive/projects/Reic05a.pdf Будем надеяться, что однажды оно осуществится.
Одной из проблем при работе с множественным наследованием является различие между наследованием интерфейса и наследованием реализации.
В C# уже есть чистая реализация наследования интерфейсов (включая выбор неявной или явной реализации) с использованием чистых интерфейсов.
Если вы посмотрите на C++, для каждого класса, который вы указываете после двоеточия в объявлении class, тип наследования, который вы получаете, определяется модификатором доступа (private, protected или public). С наследованием public вы получаете полную неразбериху множественного наследования - несколько интерфейсов смешиваются с несколькими реализациями. С наследованием private вы просто получаете реализацию. Объект «class Foo : private Bar» никогда не может быть передан функции, которая ожидает Bar, потому что это как если бы класс Foo действительно имел только частное поле Bar и автоматически реализуемый делегация шаблон.
Чистое наследование множественной реализации (которое на самом деле является просто автоматическим делегированием) не представляет никаких проблем, и было бы здорово иметь его на C#.
Что касается множественного наследования интерфейсов от классов, существует множество различных вариантов реализации этой функции. Каждый язык с множественным наследованием имеет свои собственные правила относительно того, что происходит, когда метод вызывается с тем же именем в нескольких базовых классах. Некоторые языки, такие как Common Lisp (особенно объектная система CLOS) и Python, имеют протокол метаобъектов, в котором вы можете указать приоритет базового класса.
Вот одна возможность:
abstract class Gun
{
public void Shoot(object target) {}
public void Shoot() {}
public abstract void Reload();
public void Cock() { Console.Write("Gun cocked."); }
}
class Camera
{
public void Shoot(object subject) {}
public virtual void Reload() {}
public virtual void Focus() {}
}
//this is great for taking pictures of targets!
class PhotoPistol : Gun, Camera
{
public override void Reload() { Console.Write("Gun reloaded."); }
public override void Camera.Reload() { Console.Write("Camera reloaded."); }
public override void Focus() {}
}
var pp = new PhotoPistol();
Gun gun = pp;
Camera camera = pp;
pp.Shoot(); //Gun.Shoot()
pp.Reload(); //writes "Gun reloaded"
camera.Reload(); //writes "Camera reloaded"
pp.Cock(); //writes "Gun cocked."
camera.Cock(); //error: Camera.Cock() not found
((PhotoPistol) camera).Cock(); //writes "Gun cocked."
camera.Shoot(); //error: Camera.Shoot() not found
((PhotoPistol) camera).Shoot();//Gun.Shoot()
pp.Shoot(target); //Gun.Shoot(target)
camera.Shoot(target); //Camera.Shoot(target)
В этом случае в случае конфликта неявно наследуется только реализация первого из перечисленных классов. Класс для других базовых типов должен быть явно указан, чтобы получить доступ к их реализациям. Чтобы сделать его более защищенным от идиотов, компилятор может запретить неявное наследование в случае конфликта (конфликтующие методы всегда требуют преобразования).
Кроме того, вы можете реализовать множественное наследование в C# сегодня с помощью операторов неявного преобразования:
public class PhotoPistol : Gun /* ,Camera */
{
PhotoPistolCamera camera;
public PhotoPistol() {
camera = new PhotoPistolCamera();
}
public void Focus() { camera.Focus(); }
class PhotoPistolCamera : Camera
{
public override Focus() { }
}
public static Camera implicit operator(PhotoPistol p)
{
return p.camera;
}
}
Однако он не идеален, поскольку не поддерживается операторами is и as, а также System.Type.IsSubClassOf().
+++ на этот для меня. Эйфель и Сатер - хорошие примеры правильного множественного наследования. Www.icsi.berkeley.edu/~sather/
В Sather класс не предоставляет никакого интерфейса, кроме того, который определен интерфейсами, которые он «наследует». Наследование реализации осуществляется с помощью ключевого слова include, которое делает именно это, но позволяет переименовать, чтобы избежать конфликтов пространства имен. У Sather также было много других функций, которые только недавно были добавлены в C#.
Отличный ответ. Использование неявных операторов - интересный подход, но, к сожалению, он не дает преимуществ, связанных с пониманием компилятором дерева MI. Разработчик должен поддерживать правильность любого дерева наследования.
Хорошее использование множественного наследования в C++ было как способ реализации миксинов (en.wikipedia.org/wiki/Mixin). Я думаю, что встроенная поддержка C# для миксинов (или наследования частной реализации) была бы полезной функцией.
+1. К другим комментариям о миксинах: вот почему Objective-C имеет категории (фактически миксины) и по этой причине не нуждается в MI.
Нет, мы ушли от этого. Вам это нужно сейчас.
Я предпочитаю C++. Я использовал Java, C# и т. д. По мере того, как мои программы становятся более сложными в такой объектно-ориентированной среде, мне не хватает множественного наследования. Это мой субъективный опыт.
Он может создать потрясающий спагетти-код ... он может создать удивительно элегантный код.
Интерфейсы имеют множественное наследование. Фактически, я бы считал интерфейсы типов Java / C# «правильной» реализацией множественного наследования. Принудительное множественное наследование с использованием интерфейсов вместо разрешения наследования от нескольких конкретных или абстрактных классов вынуждает разработчика использовать композицию / делегирование для повторного использования кода. Наследование никогда не должно использоваться для повторного использования кода, а отсутствие множественного наследования типов C++ вынуждает разработчиков придумывать лучше спроектированные классы.
интерфейсы предоставляют спецификации, а не код многократного использования; это контракты, а не наследство
Я рад, что в C# нет множественного наследования, хотя иногда это было бы удобно. Вместо этого я хотел бы видеть возможность предоставить реализацию метода интерфейса по умолчанию. То есть:
interface I
{
void F();
void G();
}
class DefaultI : I
{
void F() { ... }
void G() { ... }
}
class C : I = DefaultI
{
public void F() { ... } // implements I.F
}
В этом случае ((I)new C()).F() вызовет реализацию C для I.F(), а ((I)new C()).G() вызовет реализацию DefaultI для I.G().
Существует ряд проблем, которые разработчики языка должны решить, прежде чем это можно будет добавить в язык, но ни одна из них не является очень сложной, и результат покроет многие потребности, которые делают множественное наследование желательным.
Как это не то же самое, что множественное наследование?
@kitchen: Это множественное наследование, но только интерфейсов, что уже разрешено в C#. Он не сталкивается ни с одной из проблем MI, возникающих в результате наследования выполнение.
Однако это так, если вы разрешаете им предоставлять интерфейс по умолчанию (реализация по умолчанию), то есть: «class Foo: IBar = Bar, IBaz = Baz {...}»
@Jay: Я согласен с кухней: вы просите о множественном наследовании. Это наследование интерфейса, наследование поведения и представляет собой отношение IS-A. Наследование в C++ - это именно то, что вам нужно. Мне интересно понять, что вы думаете о МИ, если не об этом.
C IS-A I, но AINT-NO DefaultI. Вы уже можете наследовать от нескольких интерфейсов в C#, но только от одного базового класса, и я не предлагаю изменять это правило.
одной из действительно хороших и (в то время) новинок в DataFlex 4GL v3 + (я знаю, знаю, Data какие?) была поддержка наследования миксин - методы из любых других классов можно было повторно использовать в вашем классе; до тех пор, пока ваш класс предоставил свойства, которые использовали эти методы, он работал нормально, и не было «проблем с ромбами» или других «подводных камней», о которых нужно было беспокоиться.
я хотел бы видеть что-то подобное в C#, так как это упростило бы определенные виды абстракции и проблем конструкции.
На самом деле я пропускаю множественное наследование по одной конкретной причине ... шаблон удаления.
КАЖДЫЙ раз, когда мне нужно реализовать шаблон удаления, я говорю себе: «Хотел бы я просто унаследовать от класса, который реализует шаблон удаления с несколькими виртуальными переопределениями». Я копирую и вставляю один и тот же шаблонный код в каждый класс, реализующий IDispose, и мне это не нравится.
Отличный пример положительного примера использования МИ!
На самом деле я понял, что мне не нужно множественное наследование ... что мне действительно нужно, так это подмешивание. ЭТО позволило бы мне ввести код шаблона Dispose без необходимости множественного наследования.
Я считаю, что такие языки, как C#, должны давать программисту возможность. Тот факт, что может быть слишком сложен, не означает, что будет слишком сложен. Языки программирования должны предоставлять разработчику инструменты для создания всего, что пожелает программист.
Вы решаете использовать те API, которые уже написал разработчик, но у вас их тоже нет.
ДА! ДА! и да!
Серьезно, я всю свою карьеру занимался разработкой GUI-библиотек, и MI (множественное наследование) делает это намного проще, чем SI (одиночное наследование).
Сначала я сделал SmartWin ++ на C++ (интенсивно использовался MI), затем я сделал Gaia Ajax и, наконец, теперь Ра-Аякс, и я могу с крайней уверенностью заявить, что MI для некоторых мест управляет. Одно из таких мест - библиотеки графического интерфейса ...
И аргументы, утверждающие, что МИ «слишком сложен» и так далее, в основном выдвигаются людьми, пытающимися построить языковые войны, и принадлежат к лагерю, у которого «в настоящее время нет МИ» ...
Точно так же, как языки функционального программирования (например, Lisp) преподавались («не-Lispers») как «слишком сложные» сторонники нефункционального языка программирования ...
Люди боятся неизвестного ...
МИ ПРАВИЛА!
Наличие множественного наследования потребовало бы, чтобы система запрещала загрузку во время выполнения или создание классов, которые являются производными от классов, но которые были недоступны во время сборки, чтобы повышающие и понижающие преобразования изменяли идентичность объекта или иногда имели экземпляры производного класса. от того, который переопределяет и запечатывает виртуальный метод, использует другое переопределение для этого метода. Ни один из них не звучит очень аппетитно. Для чего недостаточно наследования интерфейсов?
Дайте C# подразумевает, и вы не пропустите множественное наследование или какое-либо наследование в этом отношении.
Нет, я не. Я использую все другие функции объектно-ориентированного проектирования для разработки того, что хочу Я использую инкапсуляцию интерфейса и объектов, и я никогда не ограничен в том, что хочу делать.
Я стараюсь не использовать наследование. Тем меньше я могу каждый раз.
Вместо множественного наследования вы можете использовать миксины, что является лучшим решением.
Если мы введем множественное наследование, мы снова столкнемся со старой проблемой Diamond C++ ...
Однако для тех, кто считает это неизбежным, мы все же можем ввести множественные эффекты наследования посредством композиции (составить несколько объектов в объекте и предоставить общедоступные методы, которые делегируют ответственность составному объекту и возвращают) ...
Так зачем беспокоиться о множественном наследовании и делать свой код уязвимым для неизбежных исключений ...
Я почти не скучаю по множественному наследованию в C#.
Если вы используете множественное наследование для определения реальной модели предметной области, то, по моему опыту, вы часто можете создать одинаково хороший дизайн, используя одиночное наследование и несколько хороших шаблонов проектирования.
Большинство мест, где множественное наследование имеет реальную ценность, - это места, где речь идет не о самом домене, а о некоторых ограничениях технологии / фреймворка, которые требуют от вас использования MI. Реализация COM-объектов с использованием ATL - очень хороший пример использования множественного наследования для реализации всех необходимых интерфейсов, необходимых COM-объекту, и элегантное решение уродливой (по сравнению с .NET) технологии. Элегантно с точки зрения того, что это фреймворк C++!
Однако сейчас у меня возникла ситуация, когда я мог бы использовать множественное наследование. У меня есть один класс, который должен наследовать функции от другого объекта домена, но он также должен быть доступен для вызовов между доменами приложений. Это означает, что он ДОЛЖЕН наследоваться от MarshalByRefObject. Итак, в этом конкретном случае я действительно хотел бы получить как от MarshalByRefObject, так и от моего конкретного объекта домена. Однако это невозможно, поэтому мой класс должен реализовать тот же интерфейс, что и мой объект домена, и переадресовывать вызовы агрегированному экземпляру.
Но это, как я уже сказал, тот случай, когда технология / фреймворк накладывает ограничение, а не сама модель предметной области.
Я говорю «нет» до тех пор, пока проблема «Бриллиант» (серьезная причина того, почему множественное наследование с классами - это плохо) не будет решена должным образом, и пока решение не будет таким же хорошим, как использование интерфейсов. Короче говоря, проблема бриллианта в основном связана с потенциальной неоднозначностью данных, методов и событий в классах из-за множественного наследования через классы.
P / S Вы «редко» избегаете крайне необходимого программного решения только потому, что оно сложно. «Сложность» не оправдывает отсутствие множественного наследования. Многопоточность сложна, но она доступна на C#. Большая проблема отсутствия множественного наследования связана с проблемой ромба (http://en.wikipedia.org/wiki/Diamond_problem). К вашему комментарию о лучших альтернативах применимы хитрые рассуждения (люди решают и думают по-разному, поэтому иногда одно решение - плохая идея) и подходящие варианты уже существуют (зачем создавать LINQ, когда ADO.NET делает свое дело и является зрелым ... из-за к силе одного над другим.)
Интересно, что вы имеете в виду под «до решения»? Любая заданная семантика наследования фреймворка будет поддерживать некоторую комбинацию аксиом (одна из них будет: «если X: Y: Z и объект x типа X, приведение x к Y, а затем Z даст тот же результат, что и приведение непосредственно к Z») . Фреймворк, который не поддерживает множественное наследование, может поддерживать комбинации аксиом, которые не могут поддерживаться в фреймворке, который поддерживает. Проблема не в разработке фреймворков - даже с семантической точки зрения MI потребует отказаться от более полезных вещей.
@supercat Говоря «пока она не будет решена», я не имею в виду, что проблема с бриллиантами решена. Я имею в виду приемлемый обходной путь. С моей стороны это был плохой выбор слов.
Достаточно справедливо, хотя стоит отметить, что для соблюдения некоторых аксиом типов C# ни один тип не может наследовать более одного другого типа, который ожидает использования его виртуальных реализаций базовых членов или ожидает, что защищенные базовые поля не будут к нему обращается любой класс, который не является ни предком, ни потомком. Это почти заставляет различать наследуемые типы, которые будут иметь такие ожидания, и те, у которых нет. В Java, .NET и C# говорят, что тип не будет выполнять проблемных операций, - это сделать его интерфейсом.
Хотя интерфейсы немного более строгие, чем множественное наследование, вещи должны быть [например, нет семантической причины, по которой интерфейс не может определять поля, и чтобы каждый реализующий тип включал скрытый метод, который возвращал бы смещение между началом его данных поля и «полем», которое реализует это поле интерфейса, а также почему интерфейс не может 'не определяют реализации "по умолчанию" для методов, определенных внутри себя (или даже тех, которые определены в предке, с явным условием, что в случае двусмысленности структура может выбрать любой подходящий метод произвольно)
Нет, если проблема с бриллиантом не будет решена. и вы можете использовать композицию, пока она не будет решена.
Было бы проще, меньше кода, если бы интерфейсы могли иметь реализацию. Но могут ли они быть использованы не по назначению, а не по назначению? Я думаю, что, пока вы не увидите, как это можно сделать должным образом, вы не упустите это. Меня раздражают аргументы, что множественное наследование небезопасно. Это не. Но почти все языковые реализации есть. Но НУЖНО ли оно нам? Я не знаю.
Я бы предпочел ковариацию возвращаемого типа, это можно легко добавить (просто ослабьте правила для типа возвращаемого значения в замещенных методах), и это всегда безопасно.
пример:
class shape {}
class circle : shape {}
interface part {
shape Form();
}
interface wheel : part {
circle Form();
}
Я не уверен, какая проблема может возникнуть при разрешении интерфейсу (например, IFoo) указывать статический класс (например, IFooHelpers), если при компиляции класса C, реализующего этот интерфейс, компилятор не может найти реализацию для члена (например, int DoSomething(string Bar) он должен автоматически сгенерировать код int IFoo.DoSomething(string Bar) {return IFooHelpers.Classes<C>.DoSomething(C This, string Bar);}, предполагая, что соответствующий статический класс и метод существуют.
Я не понимаю, насколько элегантно множественное наследование.