Как бороться с недостатком множественного наследования в С#

Я работаю над мини-картой для "управляемых" вещей. (Это эксперименты, тесты, задания и т.д.)

// 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>

Теперь, как я могу примирить ConcurrentBlockRunner и SequentialBlockRunner? Под этим я подразумеваю:

  • Обратитесь к ним общим предком, для использования в коллекции. (IEnuerable<T> где T =??)

  • Предоставить дополнительную функциональность базового класса. (Добавьте свойство, например).


Я исправил # 1, добавив еще один интерфейс, который только что задал параметр типа IA<T>:

interface IBlockRunner : IRunnableOf<Block> { }

И изменили мои определения ConcurrentBlockRunner и SequentialBlockRunner:

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

Так как ConcurrentBlockRunner и SequentialBlockRunner используют Block для своего параметра типа, это кажется правильным решением. Однако я не могу не чувствовать себя "странно", потому что хорошо, я просто применил этот интерфейс.


Для # 2, я хочу добавить пару общих данных в ConcurrentBlockRunner и SequentialBlockRunner. Существует несколько свойств, которые применяются к ним, но не только к их единственному базовому классу, который находится на уровне RunnerBase<T>.

Это первый раз, когда я использую С#, который, как я понял, поможет множественному наследованию. Если бы я мог:

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 для прокси-событий событий, но на самом деле их обрабатывает и вызывает собственное событие. Так много работы, чтобы добавить пару свойств...

Ответ 1

Состав над наследованием, FTW!

Чтобы быть более явным:

class SequentialRunner<T> : RunnerBase<T>

должен реализовать IRunnableOf<T> и прокси-сервер RunnerBase<T>, не наследуя его.

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

   ...
}

Ответ 2

Вы можете использовать методы расширения для создания mixin-подобных конструкций даже с элементами, подобными свойствам.

Я также создал эксперимент с похожими на черты конструкциями в С#, NRoles.

Но все они требуют нестандартного кодирования и не будут идеальными для API, которые должны быть подвержены третьим сторонам. Я думаю, вы должны попытаться изменить свои классы и использовать композицию с делегированием, используя интерфейсы, если это возможно.