TheEdge
TheEdge

Reputation: 9851

Declare an interface where concrete implementation has concrete types

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.

Upvotes: 1

Views: 978

Answers (1)

Eric Lippert
Eric Lippert

Reputation: 660024

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.

Upvotes: 5

Related Questions