Dmitrii Zyrianov
Dmitrii Zyrianov

Reputation: 2318

How to create a generic instance based on it generic type

I have two storages. The first is simple. The second add additional info to entity (meta). I want to write factory that create storage based on generic type of my entity. But i can't do it. I received a compilation error. Maybe i want some weird things and i should rewrite architecture. Also i tried to use reflection, but it also didn't work. Here is an example:

using System;
using System.Reflection;

namespace ConsoleApp5
{
    interface IStorage<T>
    {
        void Save();
    }

    class Storage<T> : IStorage<T>
    {
        public virtual void Save()
        {
            Console.WriteLine("Save");
        }
    }

    class StorageWithMeta<T> : Storage<T> where T : EntityWithMeta
    {
        public override void Save()
        {
            Console.WriteLine("Save With Meta");
        }
    }

    abstract class EntityWithMeta
    {
    }

    class StorageFactory
    {
        public static IStorage<T> Create<T>()
        {
            if (typeof(EntityWithMeta).IsAssignableFrom(typeof(T)))
            {
                return CreateWithMeta<T>(); //compilation error! (type T must be convertible to EntityWithMeta)

                //reflection based approach:
                //var methodInfo = typeof(StorageFactory).GetMethod("CreateWithMeta", BindingFlags.Static | BindingFlags.NonPublic);
                //return (IStorage<T>) methodInfo.Invoke(null, null); //System.InvalidOperationException. ContainsGenericParameters is true
            }
            return new Storage<T>();
        }

        private static IStorage<T> CreateWithMeta<T>() where T : EntityWithMeta
        {
            return new StorageWithMeta<T>();
        }
    }

    class MyClass1
    {
    }

    class MyClass2 : EntityWithMeta
    {
    }

    class EntryPoint
    {
        public static void Main()
        {
            StorageFactory.Create<MyClass1>().Save();//expected Save
            StorageFactory.Create<MyClass2>().Save();//expected Save With Meta
        }
    }
}

Upvotes: 2

Views: 82

Answers (1)

pinkfloydx33
pinkfloydx33

Reputation: 12799

The problem is that the compiler cannot guarantee that T is assignable to EntityWithMeta, despite your runtime check. In other words it has no idea what that if statement means in context. You can get around this by using Activator.CreateInstance and Type.MakeGenericType

return (IStorage<T>)Activator.CreateInstance(typeof(StorageWithMeta<>).MakeGenericType(typeof(T)));

A note on your reflection based approach (which you commented out in the OP): the method CreateWithMeta is itself generic. The MethodInfo object you have is for a generic method definition. You were on the right track but you have to create the constructed generic method using MethodInfo.MakeGenericMethod:

var methodInfo = typeof(StorageFactory).GetMethod("CreateWithMeta", BindingFlags.Static | BindingFlags.NonPublic);
var constructedGeneric = methodInfo.MakeGenericMethod(typeof(T));
return (IStorage<T>)constructedGeneric.Invoke(null, null);

Upvotes: 2

Related Questions