Create Generic Type with Generic Interface at Run Time

.net c# expression-trees generics linq

Question

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.

Accepted Answer

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});

Popular Answer

It's not really hard to turn run-time Type into compile-time generic Type parameter. Just introduce new interface for creating/initializing your objects:


interface IRawenWriterFactory
{
  object Create();
}

class RawenWriterFactory<T> : IRawenWriterFactory
{
  public object Create()
  {
    Expression<Func<IDataEntity, DateTime>> func = x => x.CicReadTime;

    var ravenWriter = new RavenWriterBase<T>();
    ravenWriter.Initialize(60, func);

    return ravenWriter;
  }
}

Now just create RawenWriterFactory with dataEntityType just like you've created ravenWriter and use it via non-generic IRavenWriterFactory interface.

However, there could be simpler solutions if you'll change your design. E.g. if you turn Initialize method into constructor you'll be able to pass func as Activator.CreateInstance parameter and you wouldn't need to use generic interface for initialization at all.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why