例如,我能够使用接口和三个类似的类来实现缺失的多重继承模式:
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(); }
}
是否可以像在C ++中那样将多个现有类注入一个新类中?
也许有解决方案
还是看起来像这样(虚构的c#语法):
public class FirstAndSecond: IFirst from First, ISecond from Second
{ }
这样,当我修改其中的一个时,就不需要更新FirstAndSecond类接口。
编辑
也许最好考虑一个实际示例:
您有一个现有的类(例如,基于ITextTcpClient的基于文本的TCP客户端)已经在项目内部的不同位置使用。现在您感觉需要创建类的组件,以便Windows窗体开发人员可以轻松访问。
据我所知,您目前有两种方法可以做到这一点:
编写一个从组件继承的新类,并使用该类本身的实例实现TextTcpClient类的接口,如FirstAndSecond所示。
编写一个从TextTcpClient继承的新类并且以某种方式实现了IComponent(实际上尚未尝试过)。
在两种情况下,您都需要按方法而不是按类进行工作。既然您知道我们将需要TextTcpClient和Component的所有方法,那么将这两个方法合并为一个类将是最简单的解决方案。为避免冲突,可以通过代码生成来完成,此后可以更改结果但是用手打字简直是一团糟。
#1 楼
由于多重继承不好(这会使源代码更加复杂),因此C#不会直接提供这种模式。但是,有时具有此功能会有所帮助。
C#和.net CLR尚未实现MI,因为它们尚未得出结论,认为MI如何在C#和VB.net之间互操作。和其他语言,不是因为“它会使源变得更加复杂”
MI是一个有用的概念,未解决的问题是这样的:-“当您有多个共同点时您会怎么做?
Perl是我曾经使用过的唯一能使MI正常工作的语言。.Net可能有一天会引入它,但还没有,CLR已经支持MI,但正如我所说,到目前为止,还没有语言构造。
直到您被代理对象和多个接口所困扰:(<< />
评论
CLR不支持多重实现继承,而仅支持多重接口继承(C#也支持)。
–Jordão
2010年7月26日在2:05
@Jordão:出于完整性考虑:编译器可以在CLR中为其类型创建MI。它确实有一些警告,例如,它不符合CLS。有关更多信息,请参见这篇(2004)文章blogs.msdn.com/b/csharpfaq/archive/2004/03/07/…
–dvdvorle
2012年3月1日在8:45
@MrHappy:非常有趣的文章。实际上,我已经研究了一些C#的特征组合方式,看看吧。
–Jordão
2012年3月3日,3:32
@MandeepJanjua我没有要求任何此类事情,我说“可以介绍它”。事实仍然是ECMA标准CLR确实为多重继承提供了IL机制,只是没有任何东西可以充分利用它。
–伊恩·诺顿
2012年5月8日21:44
仅供参考,多重继承也不错,也不会使代码变得那么复杂。只是以为我会提到它。
– Dmitri Nesteruk
17年4月7日在21:22
#2 楼
考虑只使用组合而不是尝试模拟多重继承。您可以使用Interfaces定义组成该组合的类,例如: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();
}
}
评论
但这就是重点-这样的想法会简化合成过程。
–乔恩·斯基特(Jon Skeet)
08年10月7日在13:15
是的,但是在某些用例中,您确实需要将方法作为主对象的一部分
–大卫·皮埃尔(David Pierre)
08年10月7日在14:34
不幸的是,成员变量数据无法在扩展方法中访问,因此您必须将其公开为内部或(ug)公共,尽管我认为按合同组合是解决多重继承的最佳方法。
–cfeduke
08-10-10在10:52
很好的答案!简洁,易于理解,非常有用的插图。谢谢!
– AJ。
08-10-20在20:11
我们可能想在调用Stop之前检查myCar是否已完成向左转向。如果在myCar速度过快时应用“停止”,则可能会翻转。 :D
–德夫拉吉·加达维(Devraj Gadhavi)
15年6月22日在13:09
#3 楼
我创建了一个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();
#4 楼
您可以有一个实现IFirst和ISecond的抽象基类,然后仅从该基类继承。评论
这可能是最好的解决方案,但不一定是最好的主意:p
–嬉皮士
08年10月7日在13:10
向接口添加方法时,是否还需要编辑抽象类?
–力克
08年10月7日在13:27
瑞克:当你只需要做一次的时候,你到底有多懒?
–嬉皮士
08-10-7 13:43
@leppie-“每次我向其中一个接口添加一个方法时,我也需要更改类FirstAndSecond。”此解决方案无法解决原始问题的这一部分,对吗?
–力克
08年10月7日14:00
您将不得不编辑抽象类,但是不必编辑依赖于它的任何其他类。责任制止于此,而不是继续级联到整个班级。
–乔尔·科恩(Joel Coehoorn)
08-10-7 14:57
#5 楼
在我自己的实现中,我发现使用MI的类/接口虽然是“好的形式”,但往往过于复杂,因为您只需要为几个必要的函数调用设置所有这些多重继承,就我而言,确实需要重复执行数十次。相反,将静态的“调用函数的函数调用不同函数”的模块化类型中的OOP替换起来更加容易。我正在研究的解决方案是RPG的“咒语系统”,其中效果需要大量混合和匹配函数调用,以提供多种多样的咒语而无需重新编写代码,就像该示例似乎表明的那样。
现在,大多数函数可以是静态的,因为我不一定需要拼写逻辑的实例,而类继承在静态时甚至不能使用虚拟或抽象关键字。接口根本无法使用它们。
IMO这样编码似乎更快,更干净。如果您只是在做函数,不需要继承的属性,请使用函数。
#6 楼
现在,使用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
}
评论
是的,但是请注意,在上面,您将无法执行新的ConsoleLogger()。Log(someEception)-它将无法正常工作,您必须将对象显式转换为ILogger才能使用默认接口方法。因此其用途受到一定限制。
– Dmitri Nesteruk
19年11月18日在13:24
#7 楼
如果您可以忍受这样的限制,即IFirst和ISecond的方法只能与IFirst和ISecond的契约进行交互(就像您的示例中一样)...您可以使用扩展方法来执行您所要求的。实际上,这种情况很少见。public interface IFirst {}
public interface ISecond {}
public class FirstAndSecond : IFirst, ISecond
{
}
public static MultipleInheritenceExtensions
{
public static void First(this IFirst theFirst)
{
Console.WriteLine("First");
}
public static void Second(this ISecond theSecond)
{
Console.WriteLine("Second");
}
}
///
public void Test()
{
FirstAndSecond fas = new FirstAndSecond();
fas.First();
fas.Second();
}
所以基本概念是您在接口中定义了所需的实现...此必需的内容应支持扩展方法中的灵活实现。任何时候您需要“向接口添加方法”而不是添加扩展方法。
#8 楼
是的,使用Interface很麻烦,因为每当我们在类中添加方法时,都必须在接口中添加签名。另外,如果我们已经有一个带有一堆方法但没有接口的类,该怎么办?我们必须为要继承的所有类手动创建Interface。最糟糕的是,如果子类要从多个接口继承,则必须在子类的Interfaces中实现所有方法。按照Facade设计模式,我们可以模拟从多个接口继承使用访问器的类。在需要继承的类中使用{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();
}
}
}
使用此结构,子类可以访问父父类的所有方法和属性,模拟多重继承,继承父类的实例类。不太一样,但是很实用。
评论
我不同意第一段。您只需将每个类中所需的方法的签名添加到接口。但是您可以根据需要向任何类添加尽可能多的其他方法。此外,还有一个右键单击提取接口,这使得提取接口的工作变得简单。最后,您的示例绝不是继承(多重或其他方式),而是一个很好的组合示例。 las,如果您使用接口还通过构造函数/属性注入来演示DI / IOC,那将有更多好处。虽然我不会投票否决,但我认为这不是一个好的答案。
–弗朗西斯·罗杰斯(Francis Rodgers)
2014-12-17 19:25
一年后回头看这个线程,我同意您可以在类中添加任意数量的方法而无需在接口中添加签名,但是,这会使接口不完整。此外,我无法在IDE中找到右键单击-提取接口,也许我缺少了一些东西。但是,我更大的担心是,当您在接口中指定签名时,继承的类必须实现该签名。我认为这是双重工作,可能导致代码重复。
–瑜伽士
2015年8月8日15:50
EXTRACT INTERFACE:右键单击类签名,然后提取接口...在VS2015中,相同的过程,除了必须右键单击,然后选择Quick Actions and Refactorings ...这是必须知道的,它将为您节省很多时间
– Chef_Code
16 Sep 26 '18:49
#9 楼
我们大家似乎都在用这种方法来简化接口的路径,但是这里明显的另一种可能性是做OOP应该做的事情,并建立您的继承树...(这不是类设计的全部吗?关于?)class Program
{
static void Main(string[] args)
{
human me = new human();
me.legs = 2;
me.lfType = "Human";
me.name = "Paul";
Console.WriteLine(me.name);
}
}
public abstract class lifeform
{
public string lfType { get; set; }
}
public abstract class mammal : lifeform
{
public int legs { get; set; }
}
public class human : mammal
{
public string name { get; set; }
}
这种结构提供了可重用的代码块,当然,应该如何编写OOP代码?
如果这个特殊的这种方法不太适合我们,我们只是根据所需对象创建新类...
class Program
{
static void Main(string[] args)
{
fish shark = new fish();
shark.size = "large";
shark.lfType = "Fish";
shark.name = "Jaws";
Console.WriteLine(shark.name);
human me = new human();
me.legs = 2;
me.lfType = "Human";
me.name = "Paul";
Console.WriteLine(me.name);
}
}
public abstract class lifeform
{
public string lfType { get; set; }
}
public abstract class mammal : lifeform
{
public int legs { get; set; }
}
public class human : mammal
{
public string name { get; set; }
}
public class aquatic : lifeform
{
public string size { get; set; }
}
public class fish : aquatic
{
public string name { get; set; }
}
#10 楼
多重继承是通常引起更多问题而不是解决的事情之一。在C ++中,它很适合给您足够的绳索来挂自己,但Java和C#选择了不给您选择的更安全的方法。最大的问题是,如果您继承多个类,这些类的方法具有与被继承者未实现的相同签名,则该方法是可行的。应该选择哪个类的方法?还是不应该编译?通常,还有另一种方法可以实现大多数不依赖多重继承的事情。评论
请不要用C ++来判断MI,这就像用PHP来判断OOP或用Pintos来判断汽车。这个问题很容易解决:在Eiffel中,从类继承时,还必须指定要继承的方法,然后可以重命名它们。那里没有歧义,也没有惊奇。
–Jörg W Mittag
08年12月13日在12:44
@mP:不,Eiffel提供了真正的多重实现继承。重命名并不意味着失去继承链,也不会失去类的可转换性。
–阿贝尔
2010年7月19日在15:33
#11 楼
如果X继承自Y,则有两个正交的效果:Y将为X提供默认功能,因此X的代码只需包含与Y不同的东西。 br />
几乎在任何地方都可以预期使用Y,而可以使用X代替。
尽管继承提供了这两种功能,但不难想象其中哪一种都可以使用没有其他就可以使用。我所知道的.net语言没有直接实现第一个而不第二个的方法,尽管可以通过定义一个从不直接使用的基类并拥有一个或多个直接从其继承而无需添加任何东西的类来获得这种功能。新类(此类可以共享所有代码,但不能互相替代)。但是,任何符合CLR的语言都将允许使用提供接口的第二个功能(可替换性)的接口,而无需提供第一个功能(成员重用)。
#12 楼
我知道我知道,尽管不允许这样做,等等,有时候您实际上需要它,所以对于那些:
class a {}
class b : a {}
class c : b {}
为此,请执行以下操作:
类b:表单(是windows.forms)
类c:b {}
因为函数的一半是相同的,并且带有接口,您必须重写它们全部
评论
您的示例未描述多重继承,那么您要解决什么问题?一个真正的多重继承示例将显示类a:b,c(实现任何必要的契约漏洞)。也许您的示例过于简化了?
– M.Babcock
2013年1月24日5:27
#13 楼
由于不时出现多重继承(MI)问题,因此我想添加一种解决合成模式问题的方法。我以
IFirst
,ISecond
,First
为基础,Second
,FirstAndSecond
方法,如问题中所述。我将示例代码简化为IFirst
,因为无论接口/ MI基类的数量如何,模式都保持不变。假设MI的
First
和Second
都源自同一个基类BaseClass
,仅使用来自BaseClass
的公共接口元素这可以通过在
BaseClass
和First
实现中为Second
添加容器引用来表示:class First : IFirst {
private BaseClass ContainerInstance;
First(BaseClass container) { ContainerInstance = container; }
public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); }
}
...
当引用来自
BaseClass
的受保护接口元素时,或者当First
和Second
将成为MI中的抽象类,要求它们的子类实现一些抽象部分时,事情会变得更加复杂。class BaseClass {
protected void DoStuff();
}
abstract class First : IFirst {
public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
protected abstract void DoStuff(); // base class reference in MI
protected abstract void DoSubClassStuff(); // sub class responsibility
}
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的实现非常复杂,并且访问的私有/受保护方法的数量适中,那么此模式可能有助于克服缺乏多重继承的问题。
#14 楼
这与劳伦斯·文纳姆(Lawrence Wenham)的回答类似,但是取决于您的用例,它可能会或可能不会有所改善-您不需要设置器。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()方法。从这一点来看,除了新的ivars,基本上所有的Person代码都输入IGetPerson,而不是IPerson,这两个都必须输入。而且在使用此类代码时,您不必担心IGetPerson对象本身是否实际上是IPerson。
评论
在某种程度上,这不仅仅是变相的多重继承,它又怎么那么简单?考虑到3.5中的新扩展方法及其工作方式(生成静态成员调用),这可能是下一种.NET语言演变。
有时我想知道为什么人们不只是... A类:B类:C类?
@NazarMerza:链接已更改。现在:多重继承问题。
不要让宣传愚弄你。您的示例显示了多重继承很有用,而接口只是缺乏继承的一种解决方法