watdo watdo - 3 months ago 68
C# Question

C# call generic interface method from non generic parent interface without dynamic

Say we have a non generic base interface, with a generic inheriting interface:

public interface IFoo { }
public interface IBar<T, K> : IFoo {
K Do(T t);
}

public class BarImpl : IBar<Type, AnotherType> {
public AnotherType Do(Type type) {
return new AnotherType(type);
}
}


I need to create a factory which returns an IFoo instance, but using the returned instance I need to be able to call the derived types Do(T), which isn't available.

public class FooFactory() {
IFoo Get() {
// simplified, in reality i am returning the correct
// type by checking the generic interface
// types to get an object from a stored list
// of implementations
return BarImpl();
}
}

// Now in another class
public void DoFoo() {
IFoo iFoo = new FooFactory().Get();
// Need to be able to call iFoo.Do(Type) but cannot
}


The only way I have been able to get this to work is to create a dynamic object, instead of IFoo, and then call Do() - which does work in my case but I lose some type safety which i'd prefer to keep.

My question is can I re-engineer this to be able to get access to the derived interfaces method, whilst still being able to maintain a list (and subsequently factory method return type) of IFoo????

Answer

You expect or want type safety, but think about it this way:

  • In order to be able to call Do, Get needs to return a type which defines that method. IFoo does not have it, but IBar<T, K> does. Get however returns an IFoo object which is not guaranteed to be a IBar<T, K>.
  • Even if the implementation of Get would make sure that only a IBar<T, K> is returned, there is no way the type system would know this without actually returning that type.
  • Assuming you could return a type that allowed you to call the Do method, the type would be unclear: You need to pass it an object of type T. But the returned IFoo or IBar<T, K> does not necessarily use the same type T that you wanted to pass to Do.
  • Even if the implementation of Get would provide this (like “Get me an IBar<T, K> that accepts the type T”) and the type system would have a way to reflect this, then this still wouldn’t say anything about K. For a known T, it could still be an IBar<T, int>, or an IBar<T, string>. And there is actually no way to know that without actually having the concrete type.
  • And still, assuming that this would work with the type system: What purpose would this actually serve? You could call the method of your generic type with your correct typed argument: But the return value still has no concrete type. You couldn’t say anything about the returned type from Do.

My point is that you only need generic types, when you actually have a reason to maintain a concrete type. Usually if you call a generic method or a method of a generic type from another non-generic method, then you either have a discrete set of types you’re working with, or you don’t actually need the generic type information.

So maybe you’re better off introducing a non-generic IBar type here:

interface IBar
{
    object Do(object t);
}
interface IBar<T, K> : IBar
{
    K Do(T t);
}

public class BarImpl : IBar<Type, AnotherType>
{
    public AnotherType Do(Type type)
    {
        return new AnotherType(type);
    }

    public object Do(object t)
    {
        return Do((Type) t);
    }
}

Then you could make Get return an IBar instead, and you have a way to call Do.

Btw. this pattern is used pretty commonly in the BCL, e.g. IEnumerable<T> and IEnumerable.

Comments