【问题标题】:Is it possible to return generic class based on enum input?是否可以根据枚举输入返回泛型类?
【发布时间】:2020-09-17 19:59:47
【问题描述】:

我正在编写一个小型电子邮件模板引擎,使用 Razor 语法来填充标记。我有几种电子邮件类型:

public enum EmailType { Welcome, Reminder }

这些类型都有对应的模板和模型,例如Welcome email 有一个模板:

<p>Welcome, @Model.Name</p>

以及相应的模型:

public class WelcomeModel { public string Name { get; set; } }

现在我想创建一个方法,为给定的枚举强制一个正确的模型,我想像这样:

public ITemplate<T> GenerateTemplate(EmailType emailType)
{
    switch (emailType)
    {
        case EmailType.Welcome:
            return new EmailTemplate<WelcomeModel>();

        case EmailType.Reminder:
            return new EmailTemplate<ReminderModel>();
    // ...
}

EmailTemplate&lt;T&gt; : ITemplate&lt;T&gt;,所以我可以链接方法:

engine
    .GenerateTemplate(EmailType.Welcome)
    .WithModel(new WelcomeModel()) // this knows it wants WelcomeModel
                                   // and should complain with compiler error otherwise

我在此处显示的代码无法编译,因为 T 未知。但是无法推断出这个T

public ITemplate<T> GenerateTemplate<T>(EmailType emailType)

这给我留下了:

engine
    .GenerateTemplate<WelcomeModel>(EmailType.Welcome)
    .WithModel(new WelcomeModel());

这行得通,但我觉得我在传递冗余信息 - 枚举和模型,而你可以从另一个中推断出一个。我不确定我是否遗漏了 C# 中的某些内容,或者我的整个概念都不好。我认为我走入了死胡同,因为我认为我不能从一个方法返回两个单独的强类型类。

是否可以根据枚举输入返回通用模型?

【问题讨论】:

  • WithModel 也是通用的吗?试着让它WithModel&lt;T&gt;(T model)
  • 我可以将WithModel 设为通用,但这样它会接受任何模型——不是我想要强制的模型
  • 基于枚举?我认为你不能在不创造一些奇怪的反射的情况下做到这一点,即使那样也可能不行。也许您可以使用分析器来强制开发人员。但是,您需要一种将枚举值映射到模型的方法。

标签: c# generics enums


【解决方案1】:

是否可以根据枚举输入返回泛型类?

没有。不是以对调用者有用的方式。

您的方法返回ITemplate&lt;T&gt;。但是T 必须在编译时定义为something。您不能将定义推迟到运行时,除非使用后期绑定(即dynamic)或非类型安全机制(这否定了使用泛型的全部意义)。

如果您可以重新构建您的问题并解释为什么您认为在调用站点不了解类型参数T 的实际含义的情况下调用返回开放泛型类型ITemplate&lt;T&gt; 的方法是合理的,这是一个有用的解决方案可以找到。

但到目前为止,正如您在问题中所述,唯一真正的答案是,不,这是行不通的,如果可以的话,也没有任何意义。

【讨论】:

    【解决方案2】:

    你就不能这样做吗?

    switch (emailType)
    {
        case EmailType.Welcome:
            return engine.GenerateTemplate().WithModel(new WelcomeModel());
    
        case EmailType.Reminder:
            return engine.GenerateTemplate().WithModel(new ReminderModel());
    }
    
    public ITemplate<T> GenerateTemplate<T>()
    {
        return new EmailTemplate<T>();
    
        // ...
    }
    
    public interface ITemplate<T>
    {
        void WithModel(T model);
    }
    
    public class EmailTemplate<T> : ITemplate<T>
    {
        void WithModel(T model)
        {
            // ...
        }
    }
    

    【讨论】:

    • 我希望引擎是可注入的,以便其他服务可以使用它engine.Generate(Enum.First).With(ModelData); 编写消息,而无需此切换逻辑
    【解决方案3】:

    不,因为 C# 不支持真正的泛型多态性和菱形运算符尚未允许编写:

    public Template<> GenerateTemplate(EmailType emailType)
    {
      switch (emailType)
      {
        case EmailType.Welcome:
            return new EmailTemplate<WelcomeModel>();
        case EmailType.Reminder:
            return new EmailTemplate<ReminderModel>();
      }
    }
    

    您唯一能做的就是通过使用非通用顶部接口来模拟它,例如:

    ITemplate<T> : ITemplate
    

    因此创建者方法将返回ITemplate:

    public ITemplate GenerateTemplate(EmailType emailType)
    {
      switch (emailType)
      {
        case EmailType.Welcome:
            return new EmailTemplate<WelcomeModel>();
        case EmailType.Reminder:
            return new EmailTemplate<ReminderModel>();
      }
    }
    

    但在你的情况下,如果你更喜欢真正的泛型,你需要创建几个方法:

    EmailTemplate<WelcomeModel> GenerateWelcomeTemplate()
      => new EmailTemplate<WelcomeModel>();
    
    EmailTemplate<ReminderModel> GenerateReminderTemplate
      => new EmailTemplate<ReminderModel>();
    

    然后你会在调用它们之前检查枚举。

    这样做更符合您的代码:

    if ( EmailType.Welcome )
      engine.GenerateWelcomeTemplate().WithModel(new WelcomeModel());
    

    这里的代码更干净。

    但我不明白你为什么在创建EmailTemplate&lt;WelcomeModel&gt; 之后提供WithModel(new WelcomeModel())...这是多余的,不是吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-28
      • 1970-01-01
      • 1970-01-01
      • 2021-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多