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。


编辑

也许考虑一个实际的例子会更好:

您有一个现有的类(例如,基于ITextTcpClient的基于文本的TCP客户端),您已经在项目中的不同位置使用它。现在,您觉得有必要创建一个类的组件,以便windows窗体开发人员可以轻松访问。

据我所知,你目前有两种方法来做到这一点:

  1. 编写一个从组件继承的新类,并使用类本身的实例实现TextTcpClient类的接口,如FirstAndSecond所示。

  2. 写一个从TextTcpClient继承的新类,并以某种方式实现IComponent(实际上还没有尝试过这个)。

在这两种情况下,您都需要为每个方法而不是每个类做工作。因为你知道我们将需要TextTcpClient和Component的所有方法,所以将这两个方法合并到一个类中是最简单的解决方案。

为了避免冲突,这可以通过代码生成来完成,结果可以在之后修改,但手动输入这是一个纯粹的痛苦。

466677 次浏览

您可以有一个实现IFirst和ISecond的抽象基类,然后从该基类继承。

考虑只使用composition而不是尝试模拟多重继承。你可以使用接口来定义组成组合的类,例如: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();
}
}

如果你可以接受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();
}

因此,基本思想是在接口中定义所需的实现……这个需要的东西应该支持扩展方法中的灵活实现。当你需要“向接口添加方法”时,你可以添加一个扩展方法。

多重继承通常是导致问题比解决问题多的事情之一。在c++中,它符合给你足够的绳子来绞死自己的模式,但Java和c#选择走更安全的路线,不给你选择。最大的问题是,如果您继承了多个类,这些类的方法具有相同的签名,而被继承者没有实现该怎么办。它应该选择哪个类的方法?或者不应该编译?通常还有另一种方法来实现大多数不依赖于多重继承的东西。

由于多重继承是不好的(它使源代码更加复杂),c#没有直接提供这样的模式。但有时候拥有这种能力是有帮助的。

c#和。net CLR还没有实现MI,因为他们还没有总结出它将如何在c#、VB.net和其他语言之间互操作,而不是因为“这会使源代码更复杂”。

MI是一个有用的概念,未回答的问题是:“当你在不同的超类中有多个公共基类时,你该怎么办?”

Perl是我使用过的唯一一种MI可以很好地工作的语言。. net可能有一天会引入它,但现在还没有,CLR已经支持MI,但正如我所说的,目前还没有针对它的语言结构。

在此之前,你会被代理对象和多个接口所困扰:(

我创建了一个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 " $(定位路径)"

在同一个程序集中,你可以这样使用它:

var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();

在另一个程序集中,你像这样使用它:

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();

如果X继承了Y,这有两个正交的效果:

  1. Y将为X提供默认功能,因此X的代码只需要包含与Y不同的东西。 几乎在任何需要用Y的地方,都可以用X代替。

尽管继承提供了这两种特性,但不难想象,在没有另一种特性的情况下,任何一种特性都可以使用。据我所知,没有一种.net语言可以直接实现第一个而不直接实现第二个,尽管人们可以通过定义一个从不直接使用的基类来获得这样的功能,并且可以有一个或多个直接从基类继承而不添加任何新内容(这样的类可以共享它们的所有代码,但不能相互替代)。然而,任何兼容clr的语言都允许使用提供接口的第二个特性(可替换性)而不提供第一个特性(成员重用)的接口。

我知道我知道 尽管这是不允许的等等,有时你实际上需要它,所以那些:

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

就像在我的情况下,我想这样做 class b: Form(对,就是windows.forms) 类c: b {}

因为有一半的函数是相同的,你必须重写它们

是的,使用接口是一个麻烦,因为每当我们在类中添加方法时,我们都必须在接口中添加签名。此外,如果我们已经有一个类有一堆方法,但没有接口怎么办?我们必须手动为所有想要继承的类创建Interface。最糟糕的是,如果子类要从多个接口继承,我们必须在子类中实现接口中的所有方法。

通过遵循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();
}
}




}

有了这个结构,类Child可以访问父类和母类的所有方法和属性,模拟多重继承,继承父类的实例。不完全一样,但很实用。

由于多重继承(MI)的问题时常出现,我想添加一种方法来解决组合模式的一些问题。

我建立在IFirstISecondFirstSecondFirstAndSecond方法的基础上,因为它在问题中提出了。我将示例代码减少到IFirst,因为无论接口/ MI基类的数量如何,模式都保持不变。

让我们假设,对于MI, FirstSecond都派生自相同的基类BaseClass,只使用来自BaseClass的公共接口元素

这可以通过在FirstSecond实现中添加BaseClass的容器引用来表示:

class First : IFirst {
private BaseClass ContainerInstance;
First(BaseClass container) { ContainerInstance = container; }
public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); }
}
...

当引用来自BaseClass的受保护接口元素时,或者当FirstSecond是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的实际实现足够复杂,并且访问的私有/受保护方法的数量适中,那么这种模式可能有助于克服缺乏多重继承的问题。

这与Lawrence Wenham的回答相似,但取决于您的用例,这可能是一种改进,也可能不是——您不需要setter。

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。在使用这样的代码时,你不需要关心你的IGetPerson对象本身是不是一个IPerson。

在我自己的实现中,我发现在MI中使用类/接口,虽然“形式很好”,但往往过于复杂,因为你需要为几个必要的函数调用设置所有的多重继承,在我的例子中,需要做几十倍的冗余。

相反,简单地在不同的模块化变体中创建静态的“调用调用函数的函数”作为一种面向对象的替代会更容易。我正在研究的解决方案是RPG的“法术系统”,其中效果需要严重混合和匹配函数调用来提供各种各样的法术,而不需要重新编写代码,就像这个例子所表明的那样。

大多数函数现在都可以是静态的,因为我不必为拼写逻辑需要实例,而类继承在静态时甚至不能使用虚拟或抽象关键字。接口根本不能使用它们。

在我看来,这样编码更快更干净。如果你只是处理函数,并且不需要继承属性,则使用函数。

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