簡體   English   中英

C#中的多重繼承

[英]Multiple Inheritance in 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 的所有方法,所以將這兩個組合到一個類中是最簡單的解決方案。

為了避免沖突,這可以通過代碼生成來完成,在代碼生成之后可以更改結果,但是手動輸入是一件很麻煩的事情。

考慮只使用組合而不是嘗試模擬多重繼承。 您可以使用接口來定義構成組合的類,例如: 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# 和 .net CLR 還沒有實現 MI,因為他們還沒有得出結論,它將如何在 C#、VB.net 和其他語言之間進行互操作,而不是因為“它會使源代碼更復雜”

MI 是一個有用的概念,未回答的問題如下:-“當您在不同的超類中有多個公共基類時,您會怎么做?

Perl 是我曾經使用過的唯一一種在 MI 工作並且運行良好的語言。 .Net 可能有朝一日會推出它,但目前還沒有,CLR 確實已經支持 MI,但正如我所說,除此之外還沒有任何語言結構。

在那之前,你會被代理對象和多個接口卡住:(

我創建了一個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();

您可以擁有一個實現 IFirst 和 ISecond 的抽象基類,然后僅從該基類繼承。

現在使用 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
}

MI並不壞,每個擁有(嚴重)使用它的人都喜歡它並且它不會使代碼復雜化! 至少不會比其他構造更復雜化代碼。 不管代碼是否在圖片中,壞代碼都是錯誤的代碼。

無論如何,我有一個很好的小解決方案,我希望分享多重繼承,它是在; http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog或者您可以按照我的信號中的鏈接... :)

這與 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。

我們似乎都在沿着接口路徑走下去,但這里明顯的另一種可能性是做 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; }
}

在我自己的實現中,我發現使用 MI 的類/接口雖然“形式良好”,但往往過於復雜,因為您只需要為幾個必要的函數調用設置所有多重繼承,就我而言,需要重復數十次。

取而代之的是,在不同的模塊化變體中簡單地制作靜態的“調用函數的函數”作為一種 OOP 替換更容易。 我正在研究的解決方案是用於 RPG 的“法術系統”,其中效果需要大量混合和匹配函數調用,以便在不重寫代碼的情況下提供極其多樣化的法術,就像這個例子似乎表明的那樣。

大多數函數現在可以是靜態的,因為我不一定需要拼寫邏輯的實例,而類繼承在靜態時甚至不能使用虛擬或抽象關鍵字。 接口根本無法使用它們。

以這種方式,IMO 編碼似乎更快、更清晰。 如果您只是在做函數,並且不需要繼承屬性,請使用函數。

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

所以基本思想是你在接口中定義所需的實現......這個必需的東西應該支持擴展方法中的靈活實現。 任何時候您需要“向接口添加方法”,而不是添加擴展方法。

是的,使用接口很麻煩,因為每當我們在類中添加方法時,我們都必須在接口中添加簽名。 另外,如果我們已經有一個包含一堆方法但沒有接口的類怎么辦? 我們必須為我們想要繼承的所有類手動創建接口。 最糟糕的是,如果子類要從多個接口繼承,我們必須在子類的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();
         }
     }


 }

有了這個結構,子類就可以訪問父類和母類的所有方法和屬性,模擬多重繼承,繼承父類的一個實例。 不太一樣,但很實用。

多重繼承是導致問題多於解決的問題之一。 在 C++ 中,它符合給你足夠的繩索來吊死自己的模式,但 Java 和 C# 選擇了不給你選擇的更安全的路線。 最大的問題是如果你繼承了多個類,這些類的方法具有相同的簽名,而被繼承者沒有實現。 應該選擇哪個類的方法? 還是不應該編譯? 通常還有另一種方法可以實現大多數不依賴於多重繼承的東西。

由於多重繼承(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 的實際實現足夠復雜並且訪問的私有/受保護方法的數量適中,那么這種模式可能有助於克服缺乏多重繼承的問題。

如果 X 繼承自 Y,則有兩個有點正交的效果:

  1. Y 將為 X 提供默認功能,因此 X 的代碼只需包含與 Y 不同的內容。
  2. 幾乎在任何需要 Y 的地方,都可以使用 X。

盡管繼承提供了這兩個特性,但不難想象在沒有另一個的情況下可以使用其中一個。 我所知道的任何.net語言都沒有直接的方法來實現第一個而沒有第二個,盡管可以通過定義一個從不直接使用的基類來獲得這樣的功能,並且有一個或多個直接從它繼承的類而不添加任何東西新的(這樣的類可以共享它們的所有代碼,但不能相互替代)。 然而,任何符合 CLR 的語言都將允許使用提供接口的第二個特性(可替換性)而沒有第一個特性(成員重用)的接口。

我知道我知道即使它不允許等等,有時你實際上需要它,所以對於那些:

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

就像我的情況一樣,我想做這個類 b:Form(是的 w​​indows.forms)類 c:b {}

因為一半的功能是相同的,並且接口你必須全部重寫

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM