Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# implement interface method with parameter of subclass type

I have this desired class hierarchy:

interface IClass
{
    string print(IClass item);
}

class MyClass : IClass
{
    // invalid interface implementation
    // parameter type should be IClass not MyClass
    string print(MyClass item)
    { return item.ToString(); }
}

I tried to solve interface implementation problem by using generic types as next with no success:

interface IClass
{
    string print<T>(T item) where T : IClass;
}

class MyClass : IClass
{
    string print<T>(T item) where T : MyClass
    { return item.ToString(); }
}

What should I do?

like image 389
Angie Avatar asked Apr 11 '13 13:04

Angie


2 Answers

Make your interface generic

interface IClass<T>  where T : IClass<T>
{
     string print(T item);
}

class MyClass : IClass<MyClass>
{
    public string print(MyClass item)
    { 
       return item.ToString(); 
    }
}
like image 158
Mehmet Ataş Avatar answered Sep 22 '22 14:09

Mehmet Ataş


It's helpful to understand why this is illegal. The feature you want is formal parameter type covariance, and very few languages offer it. (Eiffel, I think has this as a feature.) It is not often found in languages because it is not safe! Let me illustrate with an example:

class Animal {}
class Lion : Animal { public void Roar() { } }
class Giraffe : Animal { }
interface IFoo { void M(Animal a); }
class C : IFoo
{
    public void M(Lion lion) { lion.Roar(); }
}
class P
{
    public static void Main()
    {
        IFoo foo = new C();
        foo.M(new Giraffe()); 
    }
}

And we just made a giraffe roar.

If you look at all those type conversions, the only one that can sensibly be made illegal is matching C.M(Giraffe) to IFoo.M(Animal).

Now, formal parameter type contravariance is typesafe but it is not legal in C# except in some very limited circumstances. If C# supported it, which it does not, then you could safely do something like this:

interface IBar { void M(Giraffe g); }
class D : IBar
{
    public void M(Animal animal) { ... }
}
class P
{
    public static void Main()
    {
        IBar bar = new D();
        bar.M(new Giraffe()); 
    }
}

See what happened there? IFoo.M says "I can take a giraffe", and C.M says "I can accept any giraffe because in fact I can accept any animal". That would be typesafe if C# supported it, but it only supports it in two ways:

  • Contravariant generic delegate and interface conversions.
  • Contravariant method group conversions to delegate types.

An example of the first is that an expression of type IComparable<Animal> may be assigned to a variable of type IComparable<Giraffe> by the same logic: a method that compares two animals can be used where a method that compares two giraffes is needed. This was added in C# 4.

An example of the second is:

delegate void MyFunction(Giraffe g);
...
D d = new D();
MyFunction myfunc = d.M;

Again, we need a function that takes a Giraffe, and we supply one that takes any Animal. This feature was added in C# 2.

like image 28
Eric Lippert Avatar answered Sep 21 '22 14:09

Eric Lippert