在运行时创建具有通用接口的通用类型

5

我已经花了几个小时来解决一个问题,我认为我离成功不远了。我正在开发一个应用程序,我们可能会有50-100种类型的应用程序都是以相同的方式运行的。因此,我尝试将其设计成通用的,而不是创建50-100个类。以下是我的代码:

这是基本类:

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

这是界面:

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

这是我如何使用它的方法:

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

我卡在上面这行代码无法继续了:
var ravenWriter = Activator.CreateInstance(ravenWriterType);

这是我的问题:
我该如何将其用作RavenWriterBase或IRavenWriter?类似于以下内容:

ravenWriter.Initialize(60, func);

我认为需要像这样的东西,但我需要为IRavenWriter<>指定一个类型,而我还不知道它是什么:

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

如果我将鼠标悬停在ravenWriter上,我的对象就会成功出现:
enter image description here
但是现在我需要以一种通用的方式来使用它。我该如何做?
更新:
我想到了使用动态关键字,这个方法可行:
dynamic ravenWriter = Activator.CreateInstance(ravenWriterType);
ravenWriter.Initialize(60);

我有点作弊,因为我意识到每个IDataEntity的Func是相同的,所以不需要将其作为参数传递给Initialize()。然而,至少现在我可以调用Initialize()了。但既然Func是相同的,我也不应该需要泛型接口。


2
听起来像是泛型使用不当。当您需要根据不同的类型运行不同的方法,但这些类型仅在运行时已知时,您需要利用多态性。泛型对您有什么好处?在第一次使用IDataEntity时,您是否可以直接运行方法而无需使用泛型? - mellamokb
1
我同意mellamokb的观点。但是,如果你坚持要使用泛型,为什么不创建一个包装类,可以非泛型实例化。这个包装器应该有一个方法,根据传入的参数类型返回RavenWriterBase<Type>的实例。然后,实例化包装器并调用带有所请求类型作为参数的方法。这将需要一个大的switch语句,但至少不需要200个单独的类。 - dcow
2
您是否会创建其他表达式来代替 T,而不是使用 IDataEntity?或者T总是一个IDataEntity吗?如果T始终是IDataEntity,那么为什么要使用泛型呢? - Dan Busha
@DavidCowden:好主意,而且这并不难,因为我们经常使用的T4模板可以使其变得轻松和易于维护。 - mellamokb
@DanBusha 我需要泛型,因为我要将每种类型保存到RavenDB中。还会有另一个服务按类型提取这些数据。 - Bob Horn
显示剩余2条评论
2个回答

2
我的解决方案是:
  • 创建一个非通用的IRavenWriter接口
  • IRavenWriter<T>继承自IRavenWriter
  • IRavenWriter中保留ExecuteExecutionIntervalInSeconds
  • 使IRavenWriter拥有Func<DateTime>,并在您的编写器中使用它
  • Initialize移动到IRavenWriter<T>
  • 使用工厂根据类型和表达式初始化Func:

例如:

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

您需要使用:

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

我不确定我理解了。这怎么让我将ravenWriter用作RavenWriterBase <>或IRavenWriter <>? - Bob Horn
@BobHorn,不行。你不能在那里使用RavenWriterBase<>的知识。相反,你需要在那里使用IRavenWriterBase(非泛型)。那里不存在类型的编译时知识,所以你不能进行强制转换。 - Aliostad
谢谢。我刚刚编辑了我的问题,展示了如何使用dynamic来调用Initialize()函数。我认为那应该可以工作。 - Bob Horn
@BobHorn,“dynamic”关键字并不能真正解决问题。你的类型层次结构仍然混乱不堪,但是你只是忽略了类型系统而已。在我看来,这并不是一个解决方案。 - siride

1

将运行时的Type转换为编译时的泛型类型参数并不难。只需引入一个新接口来创建/初始化对象:


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

现在,只需像创建ravenWriter一样创建带有dataEntityType的RawenWriterFactory,并通过非泛型的IRavenWriterFactory接口使用它。

但是,如果您改变设计,可能会有更简单的解决方案。例如,如果您将Initialize方法转换为构造函数,则可以将func作为Activator.CreateInstance参数传递,并且根本不需要使用泛型接口进行初始化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接