Using a generic with an unspecified type parameter as a value in a Dictionary

I'm writing a method that returns a different instance of a class based on the type passed in, for a load process.

The method I'm trying to write will be used like:

Loader<MyType> loader = context.GetLoader<MyType>();
loader.Load(myTypeArray);

Internally, I'm planning on keeping a Dictionary<Type, T> with the loader as the value. However, I'm not sure what type T should be.

I could make T object, but I'd prefer to keep it as some sort of Loader<>. I know I'll need to cast it either way, but using Loader<> would give a little more type safety.

Is there any good way to implement this?

c#

Answers

answered 5 days ago John Wu #1

You have two choices. Both require you to add an interface.

  1. Add a plain ILoader (with no type arguments) interface, and implement it in your loader as class Loader<T> : ILoader. The common interface will allow you to store all loaders in a List<ILoader>.

    interface ILoader {}
    
    class Loader<T> : ILoader {}
    
    void Example()
    {
        var loaders = new List<ILoader>();
        loaders.Add( new Loader<Foo>() );
        loaders.Add( new Loader<Bar>() );
    }
    
  2. Define a new covariant interface ILoader<out T> and implement it in your Loader as class Loader<T> : ILoader<T>. The covariance will allow you to store all loaders in a List<ILoader<object>>. This option only works if T is a reference type, and is only viable if none of your interface methods accept T as an argument.

    interface ILoader<out T> 
    {
        //void SetLoader(T input);         //Does not compile due to covariance
        T GetLoader();                     //Read is allowed
    }
    
    class Loader<T> : ILoader<T> 
    {
        public void SetLoader(T input) {}  //Works when not in interface
        public T GetLoader() { return default(T); }
    }
    
    void Example()
    {
        var loaders = new List<ILoader<object>>();
        loaders.Add( new Loader<Foo>() );
        loaders.Add( new Loader<Bar>() );
    }
    

Code sample on DotNetFiddle

answered 5 days ago Eric Lippert #2

I could make T object, but I'd prefer to keep it as some sort of Loader<>. I know I'll need to cast it either way, but using Loader<> would give a little more type safety.

No it will not; this sentence contradicts itself! You say both "I know I'll need to cast" so there is already zero compile-time type safety, and that you can get more type safety, but zero is not more than zero!

Remember the function of a representation-preserving cast is precisely "I know a fact that will be true at runtime that the compiler does not know at compile time". That's the purpose of a cast.

There is no way to represent in the C# type system "this is a thing of type C<?>", so don't try to find one. You've already embraced run-time type checking, so go for it. You have an invariant that your dict[typeof(T)] produces something of type Loader<T>; the only person that can maintain that invariant is you, so don't even try to rely on the compiler to do it for you.

comments powered by Disqus