1

I defined a generic method Use<T> in an interface IInterface. I tried to make an implementation of that interface where the concrete implementation of the Use<T> method depends on the actual type T, and I want to always call the most specialized method. But it does not work:

    interface IInterface { void Use<T>(T other) where T : IInterface; }
    interface IChildInterface : IInterface { }
    class ImplementsIInterface : IInterface
    {
        public void Use<T>(T other) where T : IInterface
        {
            Debug.WriteLine("ImplementsInterface.Use(IInterface)");
        }
    }
    class ImplementsChildInterface : IChildInterface
    {
        public void Use<T>(IChildInterface other) where T : IInterface
        { // idea: if other is IChildInterface, use this method
            Debug.WriteLine("ImplementsChildInterface.Use(IChildInterface)");
        }

        public void Use<T>(T other) where T : IInterface
        { // idea: if above method is not applicable, use this method
            Debug.WriteLine("ImplementsChildInterface.Use(IInterface)");
        }
    }

Here is my main method:

    public static void Main()
    {
        IChildInterface childinterf = new ImplementsChildInterface();

        childinterf.Use(new ImplementsChildInterface()); // outputs "ImplementsChildInterface.Use(IInterface)"
                                                         // but should output "ImplementsChildInterface.Use(IChildInterface)"

        childinterf.Use(new ImplementsIInterface());     // outputs "ImplementsChildInterface.Use(IInterface)"
    }

The method that takes an IChildInterface argument is never called, although it should.

Is there a way to make this work? Or is my approach fundamentally wrong?

Note that it is a necessity that IInterface only has one method definition. I might enlarge the interface hierarchy at any time (and thus increase the number of implementations I could provide in an implementing class), but this should not lead to needing to add more method definitions in IInterface. Otherwise, the whole point of using interfaces (i.e. to be flexible) would be missed.


The answers I got so far all involve the need to cast. This is also something I don't want to do, since it makes the whole setup useless. Let me explain the broader picture of what I try to achieve:

Let's imagine we created some instance of an IInterface (like so: IInterface foo = new ImplementsChildInterface();). It will behave in a certain way, but it will always behave in the same way - no matter if we see it as an IInterface, an IChildInterface or an ImplementsChildInterface. Because, if we call some method on it, the compiler (or runtime? i don't know) will check what type it REALLY is and run the method defined in that type.

Now imagine we have two instances i1 and i2 of IInterface. They again are, under the hood, concrete implementations of IInterface, so they have a concrete behaviour, no matter through which glasses we seem them.

So when I run i1.Use(i2), the compiler (or runtime?) should be able to find out what i1 and i2 REALLY are, and run the corresponding method. Like so:

  1. Which type does i1 have? ImplementsChildInterface, ok, then I'll look at the methods there.
  2. Which type does i2 have? ImplementsIInterface, ok, then let's see if there exists a method Use(ImplementsIInterface ...). There is none, but maybe there is a fallback? ImplementsIInterface is a IInterface, so let's see if there exists a method Use(IInterface ...). Yes, it exists, so let's call it!
Kjara
  • 2,504
  • 15
  • 42
  • 2
    You use the term "concrete implementation" and "specialized" as if C# generics were like C++ templates. They're very much not. I'm not saying this to be pedantic, but to point out that there's a fundamental difference in how these languages approach overload resolution, and if you work against the model you're going to have a bad time. If you want reliable invocation of the most concrete methods, think virtual methods, not generic methods. – Jeroen Mostert Sep 08 '17 at 11:08
  • @JeroenMostert By "think virtual methods, not generic methods" do you mean to leave interfaces out of it completely? Because I thought about changing `IChildInterface` like so: `interface IChildInterface : IInterface { override void Use(T other) where T : IChildInterface; }`, but it seems this overriding is not possible. In classes it were possible, at least. I really appreciate your hint, because it makes clear that my approach is doomed. But I can't think of an alternative object model that works in C#. – Kjara Sep 08 '17 at 11:26
  • "Which type does ... have?" If you want a run-time resolution of these questions, you need virtual methods or reflection (`is`, `as`). Generics will not help you as they look only at the compile-time type of the context they are used in. `ImplementsChildInterface.Use(IChildInterface other)` can *never* be called through `IChildInterface`, because `IChildInterface` doesn't *have* that method. You can name it whatever you like, but as the signature doesn't match, it can't serve as an implementation for the interface. Only `Use(T other)` can. – Jeroen Mostert Sep 08 '17 at 11:26
  • 1
    Why do you even have *two* methods in your class? Why not use one single interface and only one method? – MakePeaceGreatAgain Sep 08 '17 at 11:29
  • You can have `Use(T other)` do runtime type inspection, or use double dispatch and give `T` a virtual method that you call (but this assumes you can get specific on what `T` is, your current problem statement is too abstract to suggest an appropriate solution). In some cases `dynamic` is appropriate, although that should obviously be used with care (but it's the only mechanism that will do C#-like overload resolution at runtime). – Jeroen Mostert Sep 08 '17 at 11:30
  • @HimBromBeere you mean like `public void Use(IInterface other) { if (other is IChildrenInterface) {...} else {...}}`? I think this might do what I want... Thanks for asking the right, most simple, question! – Kjara Sep 08 '17 at 11:36

1 Answers1

2

Neither IInterface nor IChildInterface have a member Use<T>(IChildInterface other) defined, but only Use<T>(T other).

Your class ImplementsChildInterface on the other side has a method Use<T>(IChildInterface other). As you declaring childInterf as a reference of type IChildInterface you can´t access that member, but unly those defined in the interface. So you should cast to the actual class in order to access the method accepting an instance of IChildInterface. But even then the generic implementation is used. So you should also cast your parameter to IchildInterface:

ImplementsChildInterfacechildinterf = new ImplementsChildInterface();
childinterf.Use(((IChildInterface)new ImplementsChildInterface()); 
childinterf.Use(new ImplementsIInterface());

Furthermore as you don´t use the generic type-parameter within your more specialized method, you can also omit it:

class ImplementsChildInterface : IChildInterface
{
    public void Use(IChildInterface other)
    { // idea: if other is IChildInterface, use this method
        Debug.WriteLine("ImplementsChildInterface.Use(IChildInterface)");
    }

    public void Use<T>(T other) where T : IInterface
    { // idea: if above method is not applicable, use this method
        Debug.WriteLine("ImplementsChildInterface.Use(IInterface)");
    }
}

Alternativly you may also add a method into your IChildInterface:

void Use<T>(IChildInterface other) where T : IChildInterface;

Now you can use

IChildInterface childinterf = new ImplementsChildInterface();
childinterf.Use<IChildInterface>(new ImplementsChildInterface()); // outputs "ImplementsChildInterface.Use(IInterface)"

which will print the desired output.

Yoh Deadfall
  • 2,711
  • 7
  • 28
  • 32
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • `childinterf.Use(((IChildInterface)new ImplementsChildInterface());` Casting still doesn't work – Abdul Samad Sep 08 '17 at 10:46
  • Have you also casted `childInterface` to the actual class? You need both. Alternativly let it be of that type already from its declaration: `ImplementsChildInterfacechildinterf = new ImplementsChildInterface();`. – MakePeaceGreatAgain Sep 08 '17 at 10:47