234

由于多重继承不好(它使源代码更复杂)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 的所有方法,那么将这两者组合到一个类中将是最简单的解决方案。

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

4

14 回答 14

225

考虑只使用组合而不是尝试模拟多重继承。您可以使用接口来定义构成组合的类,例如:ISteerable隐含类型的属性SteeringWheelIBrakable隐含类型的属性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();
    }
}
于 2008-10-07T13:13:31.330 回答
143

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

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

MI 是一个有用的概念,未回答的问题如下:-“当您在不同的超类中有多个公共基类时,您会怎么做?

Perl 是我曾经使用过的唯一一种在 MI 工作并且运行良好的语言。.Net 可能有朝一日会推出它,但目前还没有,CLR 确实已经支持 MI,但正如我所说,除此之外还没有任何语言结构。

在那之前,你会被代理对象和多个接口卡住:(

于 2009-11-08T07:09:29.780 回答
17

我创建了一个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();
于 2011-05-14T14:48:48.753 回答
6

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

于 2008-10-07T13:09:28.763 回答
4

现在使用 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
}
于 2018-11-13T19:41:33.410 回答
2

在我自己的实现中,我发现使用 MI 的类/接口虽然“形式良好”,但往往过于复杂,因为您只需要为几个必要的函数调用设置所有多重继承,就我而言,需要重复数十次。

取而代之的是,在不同的模块化变体中简单地制作静态“调用函数的函数”作为一种 OOP 替代品更容易。我正在研究的解决方案是 RPG 的“法术系统”,其中效果需要大量混合和匹配函数调用,以在不重写代码的情况下提供极其多样化的法术,就像这个例子似乎表明的那样。

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

以这种方式,IMO 编码似乎更快、更清晰。如果您只是在做函数,并且不需要继承属性,请使用函数。

于 2017-12-14T23:26:26.520 回答
1

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

所以基本思想是你在接口中定义所需的实现......这个必需的东西应该支持扩展方法中的灵活实现。任何时候您需要“向接口添加方法”,而不是添加扩展方法。

于 2008-10-07T13:23:19.000 回答
1

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

通过遵循外观设计模式,我们可以使用访问器模拟从多个类继承。用 {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();
         }
     }


 }

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

于 2014-01-15T18:33:58.407 回答
1

这与 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() 方法。从这一点开始,基本上所有的 Person 代码都进入 IGetPerson,而不是 IPerson,除了新的 ivars,它们都必须进入。在使用这样的代码时,您不必担心您的 IGetPerson 对象本身是否实际上是 IPerson。

于 2017-02-20T10:20:59.010 回答
1

我们似乎都在沿着接口路径前进,但这里明显的另一种可能性是做 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; }
}
于 2017-04-04T10:13:35.733 回答
0

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

于 2008-10-07T13:31:55.503 回答
0

如果 X 继承自 Y,则有两个有点正交的效果:

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

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

于 2011-08-24T14:40:29.873 回答
0

我知道我知道即使它不允许等等,有时你实际上需要它,所以对于那些:

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

就像我的情况一样,我想做这个类 b:Form(是的 w​​indows.forms)类 c:b {}

因为一半的功能是相同的,并且接口你必须全部重写

于 2012-03-28T11:35:53.613 回答
0

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

正如问题中提出的那样,我建立在IFirst, ISecond, First, Second,方法的基础上。FirstAndSecond我将示例代码缩减为IFirst,因为无论接口/ MI 基类的数量如何,模式都保持不变。

让我们假设,使用 MIFirst并且Second都将派生自同一个基类BaseClass,仅使用来自的公共接口元素BaseClass

这可以通过BaseClassFirstandSecond实现中添加容器引用来表达:

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 的实际实现足够复杂并且访问的私有/受保护方法的数量适中,那么这种模式可能有助于克服缺乏多重继承的问题。

于 2015-10-12T13:04:18.593 回答