[英]Type casting with generics
我为调度作业创建了以下抽象:
public abstract class BaseJob
{
public string? JobId { get; set; }
}
public interface IJobData
{ }
public interface IJob<in TJobData> where TJobData : IJobData
{
Task ExecuteAsync(TJobData jobData);
}
我使用工厂创建工作:
public class JobCreator<TJob, TJobData>
where TJob : IJob<TJobData>, new()
where TJobData : IJobData
{
public async Task ExecuteAsync(TJobData jobData)
{
var job = new TJob();
await job.ExecuteAsync(jobData);
}
}
作业安排如下:
JobClient.Enqueue<JobCreator<ForgotPasswordJob, ForgotPasswordJobData>>(job => job.ExecuteAsync(jobData));
调度程序会将作业脱水到作业存储并稍后对其进行水化。 我可以进入水化过程并打印类型:
Application.Common.Interfaces.JobCreator`2[Application.Jobs.ForgotPasswordJob,Application.Jobs.ForgotPasswordJobData]
在钩子里面,我可以进行补水工作
var job = schedulerHydrationMagic...
此时变量作业为 object 类型。 是否可以投射它,以访问 JobCreator 甚至更好的 ForgotPasswordJob? 我想在 BaseJob 中设置 JobId。
我试过了
var job = schedulerMagic as JobCreator<IJob<IJobData>, IJobData>
但类型检查抱怨 IJob 必须是非抽象类型。
我想您需要一个进一步的接口才能从调度程序中获取作业。 不能使用工厂接口,它有new
约束。
JobCreator
对通用TJob
参数有一个通用的new()
约束:
public class JobCreator<TJob, TJobData>
where TJob : IJob<TJobData>, new() // <- here
where TJobData : IJobData
{
public async Task ExecuteAsync(TJobData jobData)
{
var job = new TJob();
await job.ExecuteAsync(jobData);
}
}
很明显,您这样做是为了调用new TJob()
。
但它解释了为什么第一个泛型参数 - TJob
- 必须始终是非抽象类型。
如果 TJobData 的泛型类型是TJobData
IJob<IJobData>
,那么在运行时该行代码将等同于
var job = new IJob<IJobData>();
......这没有意义。 您不能在接口(或抽象 class)上调用new
。这对我们来说很难发现,但对编译器来说很容易。
强类型代码的重点是编译器在编译应用程序时会检测到这样的事情。 否则代码看起来没问题,你可以运行它,然后在运行时它发现TJob
没有默认构造函数。 最好让编译器拒绝它并让我们弄清楚,而不是认为它没问题,然后得到一个更令人困惑的运行时错误。
对此没有简单的答案,因为问题就在设计的中间。 这就是我所说的疯狂兔子洞。 这不是贬义词——我自己做过。
我的第一个建议不是弄清楚如何使 generics 工作——而是停止尝试做你正在做的事情。 真的有很多可能的实现吗? 从代码看起来只有一个。 因此,通过尝试编写一个通用版本来处理未知的未来类型,您可能会尝试解决您没有的问题。 在这种情况下,答案很简单。 不要试图解决它。 只需编写代码来安排您需要安排的任何事情,而不需要所有 generics。
如果您必须这样做,那么您需要摆脱new()
约束。 这意味着您将无法调用new TJob()
。 相反,您需要创建某种工厂接口,例如:
public interface IJobFactory
{
IJob<TJobData> Create<TJobData>() where TJobData : IJobData
}
然后你的JobCreator
看起来像这样:
public class JobCreator<TJob, TJobData>
where TJob : IJob<TJobData>
where TJobData : IJobData
{
private readonly IJobFactory _jobFactory;
public JobCreator(IJobFactory jobFactory)
{
_jobFactory = jobFactory;
}
public async Task ExecuteAsync(TJobData jobData)
{
var job = _jobFactory.Create<TJobData>();
await job.ExecuteAsync(jobData);
}
}
但这并没有解决问题。 它只是移动了它。 现在你必须弄清楚如何实现IJobFactory
。 (这已经令人困惑了,因为现在JobCreator
并没有真正创建工作 - IJobFactory
可以。)
这就是为什么我称它为兔子洞。 它只会永远持续下去。 如果有任何方法可以修改代码以摆脱问题而不是尝试解决问题,那就更好了。
一旦你得到了你需要工作的代码,没有所有的混乱,一个更大的问题的更好的解决方案 - 如果甚至有问题 - 可能会成为焦点。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.