2

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
        }
    }
}
11
  • Adding where T : EntityWithMeta to Create<T> does not solve your problem? Commented Sep 7, 2017 at 8:19
  • 1
    @Pikoh No, he would get an compile error with StorageFactory.Create<MyClass1>().Save(); because MyClass1does not derive from EntityWithMeta. Commented Sep 7, 2017 at 8:20
  • @MightyBadaboom you are right, don't know how I didn't notice that :) Commented Sep 7, 2017 at 8:22
  • Use Activator.CreateInstance to create an instance of the generic type. The compiler cannot guarantee that your T is assignable to EntityWithMeta (it knows not about your runtime check) which is why you get the message Commented Sep 7, 2017 at 8:29
  • 1
    Activator.CreateInstance<StorageWithMeta<T>>() but if the compiler barks at that too (can't recall if it does but I think it might) then (IStorage<T>)Activator.CreateInstance(typeof(StorageWithMeta<>).MakeGenericType(new[]{typeof(T)})) Commented Sep 7, 2017 at 8:35

1 Answer 1

2

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);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.