【问题标题】:How to deal with Lack of Multiple Inheritance in C#如何处理 C# 中缺少多重继承
【发布时间】:2012-06-11 23:20:23
【问题描述】:

我正在为“可运行”的东西开发一个迷你框架。 (它们是实验、测试、任务等)

// Something that "runs" (in some coordinated way) multiple "runnable" things.
interface IRunnableOf<T> where : IRunnable

// Provide base-class functionality for a "runner"
abstract class RunnerBase<T> : IRunnableOf<T>


class SequentialRunner<T> : RunnerBase<T>  // Same interface, different behavior.
class ConcurrentRunner<T> : RunnerBase<T>
// other types of runners.

class ConcurrentBlockRunner : SequentialRunner<Block>
class SequentialBlockRunner : ConcurrentRunner<Block>

现在,我该如何协调 ConcurrentBlockRunnerSequentialBlockRunner?我的意思是:

  1. 由共同的祖先引用它们,以便在集合中使用。 (IEnuerable&lt;T&gt; 其中 T = ??)

  2. 提供额外的基类功能。 (例如,添加一个属性)。


我通过添加另一个只为IA&lt;T&gt; 指定类型参数的接口来补救#1:

interface IBlockRunner : IRunnableOf<Block> { }

并将我的 ConcurrentBlockRunnerSequentialBlockRunner 定义修改为:

class ConcurrentBlockRunner : SequentialRunner<Block>, IBlockRunner
class SequentialBlockRunner : ConcurrentRunner<Block>, IBlockRunner

由于ConcurrentBlockRunnerSequentialBlockRunner 都使用Block 作为它们的类型参数,这似乎是一个正确的解决方案。但是,我不禁对此感到“奇怪”,因为我只是添加了那个界面。


对于#2,我想向ConcurrentBlockRunnerSequentialBlockRunner 添加一些公共数据。有几个属性适用于它们,但不适用于它们唯一的公共基类,一直到 RunnerBase&lt;T&gt;

这是我第一次在使用 C# 时觉得多重继承会有所帮助。如果可以的话:

abstract class BlockRunnerBase {
   int Prop1 { get; set; }
   int Prop2 { get; set; }

class ConcurrentBlockRunner : SequentialRunner<Block>, BlockRunnerBase
class SequentialBlockRunner : ConcurrentRunner<Block>, BlockRunnerBase

然后我可以简单地将这些额外的属性添加到 BlockRunnerBase,一切都会正常工作。有没有更好的办法?


我知道我会立即被推荐考虑composition,我开始使用它:

class BlockRunner : IBlockRunner  {
   IBlockRunner _member;

   int Prop1 { get; set; }    // Wish I could put these in some base class
   int Prop2 { get; set; }       

   // Lots of proxy calls, and proxy events into _member
   void Method() { _member.Method(); }
   event SomeEvent
   {
      add { _member.SomeEvent += value; }
      remove { _member.SomeEvent -= value; }
   }
}

我遇到的问题(促使我写这个问题)是一旦你写了,你就失去了类型兼容性。在我的例子中,_member 触发了一个事件,所以sender 参数的类型是SequentialBlockRunner。但是,事件处理程序试图将其转换为类型BlockRunner,这当然失败了。那里的解决方案不是使用add/remove 来代理事件,而是实际上处理它们,并引发我自己的事件。仅仅为了添加几个属性就做了这么多工作......

【问题讨论】:

  • 如果你需要多重继承——你的设计很糟糕。你的实际情况是什么,没有AB等代号?
  • 在其他语言中存在多重继承的有效用例...例如 C++。但是 C++ 没有接口和扩展方法。
  • @Yorye,将多重继承请求一概驳回为“糟糕的设计”,这表明您不理解。 C++ 设计者添加 MI 支持是有原因的,我相信 Bjarne 比你聪明得多。在其他语言中也是如此。 MI 很难做好,但也非常有用。
  • 您是否考虑过以不同的方式解决这个问题,也许不是从传统的 OOP 角度来解决这个问题,而是考虑完全不同的东西,例如 AOP?那么,您可以使用方面向某些 IRunnable 注入“BlockRunner”行为吗?这些天,当我遇到 MI 问题时,我开始自动思考“方面”而不是“对象”。
  • @Yorye:C# 迎合的程序员和 C++ 设计的程序员之间存在很大差异。 C# 放弃了许多特性,不是因为它们没有用,而是因为它们很难正确使用(有时对程序员来说,更常见于编译器编写者)。例如,C# 没有提供任何接近 C++ 模板的功能。这不是“我们在 20 年的经验中学到了一些东西”,这是“足够熟练的程序员处理模板和他们的错误消息不是在寻找一种新的语言,让我们为那些没有的程序员制作一种更简单的语言。”

标签: c# oop multiple-inheritance


【解决方案1】:

Composition over Inheritance,FTW!

更明确地说:

class SequentialRunner<T> : RunnerBase<T>

应该实现IRunnableOf&lt;T&gt;并代理RunnerBase&lt;T&gt;而不继承它。

class SequentialRunner<T> : IRunnableOf<T>
{
   private readonly RunnerBase<T> _runnerBase;

   ...
}

【讨论】:

  • 我同意,并且正在做很多改变以完成这项工作。但是,有两大缺点:1) 代码重复(用于方法和事件的代理) 2) 失去“基本”类型兼容性。
  • 扩展方法和混入有点不合时宜。应该鼓励@JonathonReinhart API 的使用者针对他们能做的最抽象的东西进行编程。这意味着将内脏隐藏在一个漂亮的闪亮外壳中。围绕sealed string 类问题不使用微软的语法糖破解(尽管它们很好)。
  • @MarkSowul 你如何扩展方法来添加属性?
  • 一个具有足够复杂逻辑的属性,您希望避免重复它几乎肯定更适合作为一种方法。 msdn.microsoft.com/en-us/library/…
  • 逻辑足够复杂的属性可能不是属性,需要提升自己的类。
【解决方案2】:

您可以使用扩展方法来创建mixin-like constructs,即使使用类似属性的元素。

我还在 C# 中创建了一个 experiment,其中包含类似 trait 的构造,NRoles

但是,所有这些都需要非标准编码,并且不适合打算向第三方公开的 API。如果可能的话,我认为您应该尝试重新排列您的类并使用带有委托的组合。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-07
    • 2018-09-11
    • 2019-12-05
    • 2016-09-21
    • 2011-12-15
    • 2020-09-23
    • 2014-09-30
    相关资源
    最近更新 更多