1
public interface ISomeInterface
{
    IOut SomeMethod(IIn aIn)
}

public class MyOut : IOut
{
    public string AnExtraProp {get; set;}
}

public class MyIn : IIn
{
    public string AnotherExtraProp {get; set;}    }
}

public class MyConcreteOfSomeInterface : ISomeInterface
{
     public MyOut SomeMethod(MyIn aIn)
     {
     }
} 

Is it possible to have many classes (eg. MyConcreteOfSomeInterface, MyConcrete2OfSomeInterface, ....) implement an interface (eg. ISomeInterface) but yet have parameters of a concrete type (eg. MyIn, MyOut etc.).

I realise I could declare:

public interface ISomeInterface<TIn, TOut>
{
    TOut SomeMethod(TIn aIn)
}

but as ISomeInterface will have many methods this will not be practical. So say I need to add additional methods SomeMethod2 and SomeMethod3 then I would end up with:

public interface ISomeInterface<TIn, TOut, TIn2, TOut2, TIn3, TOut3>
{
    TOut SomeMethod(TIn aIn)
    TOut2 SomeMethod(TIn2 aIn)
    TOut3 SomeMethod(TIn3 aIn)
}

so the declaration becomes unwieldy pretty quickly.

What design pattern can I use to achieve:

  1. Many concrete classes implementing an interface ISomeInterface AND
  2. Using concrete parameters/return values that are implementing the necessary interfaces IIn, IOut?

There will be many methods on ISomeInteface with different types for the parameter/interface combos.

TheEdge
  • 9,291
  • 15
  • 67
  • 135
  • 1
    Do really need the concrete types? what is the point of the interfaces you are declaring if you don't plan on using them? – Scott Chamberlain Mar 15 '18 at 03:35
  • @ScottChamberlain I want to ensure all the concretes implement what is defined in those interfaces *and* allow descendants to add additional properties etc. – TheEdge Mar 15 '18 at 03:37
  • Your final code example said that was not practical, what about doing `public interface ISomeInterface { TOut SomeMethod(IIn aIn) where TOut: IOut where TIn : IIn; }`, It will be the callers responsablity to set the types of TOut and TIn when calling SomeMethod. – Scott Chamberlain Mar 15 '18 at 03:45
  • So now I add a second methid TOut2 SomeMethod2(TIn2 aIn) and then a third TOut3 SomeMethod3(TIn3 aIn)..... Eventually my interface declaration is going to be huge. – TheEdge Mar 15 '18 at 03:47
  • It might be because its lunch time here in Brisbane, but this is hard to understand what you want to do and don't want to do. – TheGeneral Mar 15 '18 at 03:49
  • Generic Extension methods wont help you? i mean you could implement the logic once for your interface types – TheGeneral Mar 15 '18 at 03:51

1 Answers1

4

Let's simplify the problem. Suppose we have:

class Animal {}
class Giraffe : Animal {}
interface IFoo 
{
  Animal M(); 
}

Can we then have

class C : IFoo
{
  public Giraffe M() => new Giraffe();
}

Unfortunately no. An interface implementation must match exactly.

Now, you might think "hey, the interface demands that an animal be returned, and I am returning an animal, namely, a giraffe, so what's the problem?"

The answer is that there is no problem. C# could have a type system where this works, and this feature has been proposed many many many times. It's called "return type covariance", and if you do a search here you'll find many questions about it.

However C# does NOT have this feature, and so you're out of luck. The best you can do is:

class C : IFoo 
{
  Animal IFoo.M() => this.M();
  public Giraffe M() => new Giraffe();
}

And now you're good. The IFoo contract is explicitly implemented, and the public surface of the class has the more specific signature.

Similarly, what if we had:

interface IBar() 
{
  void N(Giraffe g); 
}

This is not legal:

class D : IBar
{
  public void N(Animal g) { ... }
}

Again, this would be perfectly sensible. IBar requires that D.N be a thing you can pass a giraffe to, and D.N is a thing that you can pass a giraffe or any animal to. But again, C# does not support this feature. This is called formal parameter contravariance and a very small number of programming languages support it.

Do a search on C# covariance and contravariance for details on what kinds of variance are supported by C#.

Also, note that this would not be typesafe:

interface IBaz 
{
  void P(Animal a);
}
class E : IBaz
{
  public void P(Giraffe g) { } 
}

Because you need to be able to say ((IBaz)(new E())).P(new Tiger()). IBaz says that an implementation must be able to accept any animal, so you cannot implement it with a method that only accepts giraffes. Logically it would be safe for return types to get more specific, but formal parameter types have to get less specific. That's why it's return type covariance but formal parameter type contravariance, because the direction of convertibility changes in the contra case.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • awesome answer. So in short not supported by C#. Any recommendations of a different approach? – TheEdge Mar 15 '18 at 04:02
  • @TheEdge: I gave my recommendation. Implement the interface exactly with an explicit interface implementation, and then implement the more specific / more general API as a public method on the class. Forward the interface methods to the public methods or vice versa. – Eric Lippert Mar 15 '18 at 04:03
  • Eric Lippert has previously explained a similar scenario regarding inheritance. See https://stackoverflow.com/a/5709191/18192 – Brian Mar 16 '18 at 13:28