Search code examples
c#.netlinqgenericsexpression-trees

Create Generic Type with Generic Interface at Run Time


I've been working through an issue for a couple of hours now, and I think I'm close. I'm working on an app where we could have 50-100 types that perform the same way. So instead of creating 50-100 classes, I tried to make it generic and this is what I have:

This is the base class:

public class RavenWriterBase<T> : IRavenWriter<T> where T : class, IDataEntity

And this is the interface:

public interface IRavenWriter<T>
{
    int ExecutionIntervalInSeconds { get; }
    void Execute(object stateInfo);
    void Initialize(int executionIntervalInSeconds, Expression<Func<T, DateTime>> timeOrderByFunc);
}

And this is how I'm using it:

private static void StartWriters()
{
    Assembly assembly = typeof(IDataEntity).Assembly;
    List<IDataEntity> dataEntities = ReflectionUtility.GetObjectsForAnInterface<IDataEntity>(assembly);

    foreach (IDataEntity dataEntity in dataEntities)
    {
        Type dataEntityType = dataEntity.GetType();
        Type ravenWriterType = typeof(RavenWriterBase<>).MakeGenericType(dataEntityType);

        Expression<Func<IDataEntity, DateTime>> func = x => x.CicReadTime;

        // This is where I'm stuck. How do I activate this as RavenWriterBase<T>?
        var ravenWriter = Activator.CreateInstance(ravenWriterType);

        //ravenWriter.Initialize(60, func);  // I can't do this until I cast.

        // More functionality here (not part of this issue)
    }
}

I'm stuck on this line from above:

var ravenWriter = Activator.CreateInstance(ravenWriterType);

This is my question:
How can I use that as RavenWriterBase or IRavenWriter? Something like:

ravenWriter.Initialize(60, func);

I think it needs to be something like this, but I need to specify a type for IRavenWriter<> and I don't know it yet:

var ravenWriter = Activator.CreateInstance(ravenWriterType) as IRavenWriter<>;

If I hover over ravenWriter, I successfully have my object:

enter image description here

But now I need to be able to use it in a generic way. How can I do that?

Update:

I just thought of using the dynamic keyword, and this works:

dynamic ravenWriter = Activator.CreateInstance(ravenWriterType);
ravenWriter.Initialize(60);

I cheated a bit because I realized that the Func was the same for each IDataEntity, so that wasn't necessary to pass as a parameter to Initialize(). However, at least now I can call Initialize(). But now that the Func is the same, I shouldn't need the generic interface either.


Solution

  • My solution would be to:

    • Create a non-generic interface of IRavenWriter
    • Make IRavenWriter<T> inherit from IRavenWriter
    • Keep Execute and ExecutionIntervalInSeconds in IRavenWriter
    • Make IRavenWriter have Func<DateTime> and use that in your writer
    • Move Initialize to IRavenWriter<T>
    • Use a factory to initialise the Func according to the type and expression:

    For example:

    public class MyDateTime
    {
        public DateTime This { get; set; }
    }
    
    public static Func<DateTime> GetFunk<T>(Expression<Func<T, DateTime>> timeOrderByFunc, T t)
    {
        return () => timeOrderByFunc.Compile()(t);
    }
    

    And you use:

    GetFunk<MyDateTime>(x => x.This, new MyDateTime(){This = DateTime.Now});