Ben Ford
Ben Ford

Reputation: 1394

Generic Implementation of interface with specified type

I have an interesting situation where I'd like to use a base class utilising a type parameter to implement an interface and also keep things DRY with inheriting classes.

public interface ICalculator
{
    void Process(ICalculationModel calculationModel);
}

public abstract class CalculatorBase<T> :ICalculator where T : ICalculationModel
{
     // Compiler moans that Process(ICalculationModel calculationModel) isn't implemented
     public abstract void Process(T calculationModel);
}

public class PipeworkInspections : CalculatorBase<GasSafetyModel>
{
    public override void Process(GasSafetyModel documentModel){
        //snip
    }
}

Is there something i'm missing with the generic 'where' clause or something? In my head this should work. Or does the compiler need EXACTLY the same implementation as the interface definition?

I can't easily move the type parameter into the ICalculator as there are a lot of places that it is used without any requirement for the generic.

That's cleared things up. Thanks for the info. Now obviously a solution is to make the interface take the type parameter. However ICalculator's are used in a number of places and are referenced just as ICalculator I now get compiler errors if I omit the type parameter in Interfaces that refer to ICalculator... Is there a way to architect this that should work!?

Upvotes: 5

Views: 1326

Answers (5)

Eric Lippert
Eric Lippert

Reputation: 660024

In my head this should work.

The problem then is in your head! :-) This should not work. Let's see why.

interface ICage
{
    void Enclose(Animal animal);
}
class ZooCage<T> : ICage where T : Animal
{
    public void Enclose(T t) { ... }
}
...

var giraffePaddock = new ZooCage<Giraffe>();
var cage = (ICage)giraffePaddock;
var tiger = new Tiger();
icage.Enclose(tiger);

And now there is a tiger in the giraffe paddock, and life is good for the tiger but bad for the giraffes. That's why this is illegal.

Or does the compiler need EXACTLY the same implementation as the interface definition?

The member which implements an interface member must exactly match the signature of the implemented method. For example, you cannot use return type covariance:

interface I
{
    Animal GetAnimal();
}
class C : I
{
    public Giraffe GetAnimal() { ... } // not legal.
}

The contract requires an animal; you provide a giraffe. That should work, logically, but this is not legal in C#. (It is in C++.)

See any of the many questions on this site about return type covariance for the reasons why.

Similarly for parameter type contravariance:

interface I
{
    void PutMammal (Mammal mammal);
}
class C : I
{
    public PutMammal(Animal animal) { ... } // not legal.
}

Again, this is logically sensible; the contract requires that you take a mammal, and this takes any animal. But again, this is not legal.

There are some covariant and contravariant operations in C#; see any of numerous questions on those topics on this site, or browse the covariance and contravariance articles on ericlippert.com or my previous msdn blog.

Upvotes: 8

bastio84
bastio84

Reputation: 101

I agree with Eric Lippert's response: you can't. And he explained in a very good way why this happens.

If you really want to do this, you can add the following to your abstract class, and it will compile:

void ICalculator.Process(ICalculationModel calcMod)
{
   Process((T)calcMod);
}

But you need to know what you are doing, otherwise you might have some InvalidCastException at runtime.

Upvotes: 0

Selman Gen&#231;
Selman Gen&#231;

Reputation: 101681

Yes, you need the exact implementation.

As an alternative you can make interface and Process method generic if it works for you:

public interface ICalculator<T> where T : ICalculationModel
{
    void Process(T calculationModel);
}

public abstract class CalculatorBase<T> : ICalculator where T : ICalculationModel
{
    public abstract void Process(T calculationModel);
}

Upvotes: 0

nvoigt
nvoigt

Reputation: 77294

Your interface says any class implementing it will provide this method:

void Process(ICalculationModel calculationModel);

Now obviously PipeworkInspections does not. It does not have a method Process that accepts any ICalculationModel. IT only has a method accepting specific implementations of ICalculationModel. So your compilation fails.

Upvotes: 0

Sean
Sean

Reputation: 62472

If this worked then you'd be able to say something like this:

PipeworkInspections pipeworks = new PipeworkInspections();
ICalculator calculator = pipeworks;

NuclearPowerSafetyModel nuclearModel = new NuclearPowerSafetyModel();
calculator.Process(nuclearModel); // <-- Oops!

That's probably not what you wanted...

Upvotes: 0

Related Questions