The code below is a minimal example I wrote to illustrate my question. I think it's more useful than me trying to explain the situation in words.
I also removed all references to WPF, ICommand, IDisposable, etc, because I want to discourage answers and comments that ask me "Why don't you just use Prism or some other random library?!"
I'm asking questions because I want to learn more about the language and have noone to talk to in real life.
public interface IRedDependency { }
public interface IBlueDependency { }
public interface IExecutable {
public void Execute();
}
public interface IStoppable {
public void Stop();
}
public class RedExecutable : IExecutable, IStoppable {
public RedExecutable(IRedDependency redDependency) { }
public void Execute() { }
public void Stop() { }
}
public class BlueExecutable : IExecutable, IStoppable {
public BlueExecutable(IBlueDependency blueDependency) { }
public void Execute() { }
public void Stop() { }
}
public class Example {
public Example(
IRedDependency redDependency,
IBlueDependency blueDependency)
{
RedExecutable = RememberToStopExecutable(
new RedExecutable(redDependency));
BlueExecutable = RememberToStopExecutable(
new BlueExecutable(blueDependency));
}
public IExecutable RedExecutable { get; }
public IExecutable BlueExecutable { get; }
private readonly List<IStoppable> _stoppables = new();
private IExecutable RememberToStopExecutable(IStoppable stoppable) {
_stoppables.Add(stoppable);
return (IExecutable)stoppable;
}
public void Stop() {
foreach (IStoppable stoppable in _stoppables) {
stoppable.Stop();
}
}
}
It does what I expect it to do.
But I don't like the fact that I have to do an explicit cast from IStoppable
to IExecutable
in RememberToStopExecutable
, because I can't be sure, that the IStoppable
I pass to the method also implements IExecutable
.
One idea I had was to define an Interface IExecutableAndStoppable
which inherits from both IExecutable
and IStoppable
, in the hopes that I could use this Interface as the type of the stoppable
parameter. Unfortunately this doesn't work, because the IDE says one can't
Here is the not valid code anywasy: The valid code for the Interface:
public interface IExecutableAndStoppable : IExecutable, IStoppable {}
with the valid code for the method:
private IExecutable RememberToStopExecutable(
IExecutableAndStoppable stoppable)
{
_stoppables.Add(stoppable);
return stoppable;
}
but the code where I call the method is not valid anymore:
RedExecutable = RememberToStopExecutable(
new RedExecutable(redDependency));
because, as the IDE says:
Arugment type
RedExecutable
is not assignable to parameter typeIExecutableAndStoppable
.
which is not obvious to me, since IExecutableAndStoppable
inherits from both IExecutable
and IStoppable
, but so does RedExecutable
.
Solution Attempt 1
Sure, I could make RedExecutable
and BlueExecutable
also implement IExecutableAndStoppable
like this:
public class RedExecutable : IExecutable, IStoppable, IExecutableAndStoppable { /*code*/ }
in which case my IDE even tells me:
Base interfaces IExecutable and IStoppable are redundant because RedExecutable already implements IExecutableAndStoppable
so I can simply write:
public class RedExecutable : IExecutableAndStoppable { /*code*/ }
public class BlueExecutable : IExecutableAndStoppable { /*code*/ }
Then it's all good.
Solution Attempt 2
But what if I don't really want to make this change to RedExecutable
and BlueExecutable
just for the one use-case where I want to pass them into my small private method RememberToStopExecutable
? Maybe there could be situation where I for whatever reason can't simply change the code of RedExecutable
and BlueExecutable
.
Then I could maybe make the method RememberToStopExecutable
take two parameters, one of type IExecutable
the other of type IStoppable
, but pass the same object to both.
I'd keep RedExecutable
and BlueExecutable
as they were originally, i.e.
public class RedExecutable : IExecutable, IStoppable { /*code*/ }
public class BlueExecutable : IExecutable, IStoppable { /*code*/ }
and write
private IExecutable RememberToStopExecutable(
IExecutable executable,
IStoppable stoppable)
{
_stoppables.Add(stoppable);
return executable;
}
Then I could do this:
RedExecutable redExecutable = new RedExecutable(redDependency);
RedExecutable = RememberToStopExecutable(redExecutable, redExecutable);
but well, this looks pretty weird because I'd have to pass the same object to both parameters and because I need to define a temporary variable redExecutable
.
Question
Is there no way around using type-casts in this situation?
I feel like I surely must be missing some super trivial and obvious asnwer here.
All I want is to be able to write a function like the origianl
private IExecutable RememberToStopExecutable(IStoppable stoppable) {
_stoppables.Add(stoppable);
return (IExecutable)stoppable;
}
just without the weird type-casting.
Like, just have a method that expects arguments that implement both of two interfaces. Then allow the allow the function to treat the argument as if it's implementing either of the interfaces. How can I do that?
I googled and this might be related to Intersection-Types which C# doesn't have. So how to people do this in C# ?
CodePudding user response:
You can make your method generic, with type constraints such that the single object passed in must be both executable and stoppable:
private IExecutable RememberToStopExecutable<T>(T stoppable)
where T : IStoppable, IExecutable
{
_stoppables.Add(stoppable);
return stoppable;
}
CodePudding user response:
If you can make the restriction that if your class is IStoppable
, then it is also IExecutable
, then you could make IStoppable
inherit from IExecutable
.
interface IStoppable : IExecutable
{
void Stop();
}
If you cannot make this restriction, then you can make a safe cast from IExecutable
to IStoppable
and then test if the cast succeeded.
var stoppable = executable as IStoppable;
if (stoppable != null)
{
_stoppables.Add(stoppable);
}
or using pattern matching:
if (executable is IStoppable stoppable)
{
_stoppables.Add(stoppable);
}