5

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?

Angie
  • 319
  • 3
  • 12
  • You should make the interface itself generic and give the implementing class the desired type. – Rik Apr 11 '13 at 13:19
  • 1
    How about not having the parameter in the print method and make each subclass inject the IClass implementation into the contructor or a sertter. – Sachin Kainth Apr 11 '13 at 13:19
  • 1
    The toString Method is inherited from Object, so why do you really need to have `print(MyClass item)` and not just `print(Object item)`? Do you hide this method? – Thomas Apr 11 '13 at 13:19
  • It's just a simplified example of my real class hierarchy. – Angie Apr 11 '13 at 13:26

4 Answers4

14

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(); 
    }
}
Mehmet Ataş
  • 11,081
  • 6
  • 51
  • 78
  • 2
    Note that though this does work, (1) it is confusing, and (2) it does not necessarily enforce the restriction you wish to enforce. See http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx for some thoughts on this pattern. – Eric Lippert Apr 11 '13 at 13:45
  • @Eric, What do you suggest to solve the problem in a more clear manner? – Angie Apr 12 '13 at 10:14
  • can we use polymorphism with this solution? – Angie Apr 12 '13 at 19:05
11

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
0

You just need to pass IClass as parameter to your method.

interface IClass
{
  string print(IClass item);
}

class MyClass : IClass
{
  public string print(IClass item)
  { return item.ToString(); }
}
vcRobe
  • 1,671
  • 3
  • 17
  • 35
  • 1
    My requirement is that the print method of MyClass should receive parameter of exactly MyClass and not of another IClass subclasses. – Angie Apr 11 '13 at 13:39
  • For now if we just ignore that one should program to interfaces - not implementation. whats wrong with the following interface? interface IClass { string print(MyClass item); } – WPFAbsoluteNewBie Apr 11 '13 at 14:10
  • Nothing. That's fine. But like said below @Eric Lippert it's confusing – vcRobe Apr 11 '13 at 14:21
0

Here's A solution that will get you compiling at least:

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

class MyClass : IClass
{   
    string print(IClass item) => item is MyClass i ? i.ToString() : string.Empty;
}
Tod
  • 2,070
  • 21
  • 27