Home > Enterprise >  Require method arguments to implement multiple interfaces in C#
Require method arguments to implement multiple interfaces in C#

Time:09-09

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 type IExecutableAndStoppable.

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);
}
  • Related