繁体   English   中英

用 generics 铸造

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM