I am working on a mini-framework for “runnable” things. (They are experiments, tests, tasks, etc.)
// 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>
Now, how can I reconcile ConcurrentBlockRunner and SequentialBlockRunner? By this I mean:
-
Refer to them by a common ancestor, for use in a collection. (
IEnuerable<T>where T = ??) -
Provide additional base class functionality. (Add a property, for example).
I remedied #1 by adding another interface that just specified a type parameter to IA<T>:
interface IBlockRunner : IRunnableOf<Block> { }
And modified my ConcurrentBlockRunner and SequentialBlockRunner definitions to be:
class ConcurrentBlockRunner : SequentialRunner<Block>, IBlockRunner
class SequentialBlockRunner : ConcurrentRunner<Block>, IBlockRunner
Since ConcurrentBlockRunner and SequentialBlockRunner both use Block for their type parameter, this seems to be a correct solution. However, I can’t help but feel “weird” about it, because well, I just tacked that interface on.
For #2, I want to add a couple pieces of common data to ConcurrentBlockRunner and SequentialBlockRunner. There are several properties that apply to them, but not to their only common base class, which is all the way up at RunnerBase<T>.
This is the first time while using C# that I’ve felt multiple inheritance would help. If I could do:
abstract class BlockRunnerBase {
int Prop1 { get; set; }
int Prop2 { get; set; }
class ConcurrentBlockRunner : SequentialRunner<Block>, BlockRunnerBase
class SequentialBlockRunner : ConcurrentRunner<Block>, BlockRunnerBase
Then I could simply add these extra properties to BlockRunnerBase, and everything would just work. Is there a better way?
I know I will be recommended immediately to consider composition, which I began to work with:
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; }
}
}
The problem I encountered (driving me to write this question) was that once you compose, you lose type compatibility. In my case, _member was firing an event, so the sender parameter was of type SequentialBlockRunner. However, the event handler was trying to cast it to type BlockRunner, which of course failed. The solution there is not use add/remove to proxy the events, but actually handle them, and raise an event of my own. So much work just to add a couple properties…
Composition over Inheritance, FTW!
To be more explicit:
should implement
IRunnableOf<T>and proxy theRunnerBase<T>without inheriting it.