【问题标题】:Use an enum to select which class to instantiate使用枚举选择要实例化的类
【发布时间】:2013-07-24 13:44:16
【问题描述】:

我有一个我想与 dto 关联的枚举:

 public enum DtoSelection
 {
     dto1,
     dto2,
     dto3,
 }

这个枚举中有 108 个和值。

每个 dto 都有一个 dto 对象:

 public class dto1 : AbstractDto
 {
       public int Id { get; set; }
       //some stuff specific to this dto
 }

我正在尝试创建一个方法(最终是一个服务),它将返回一个新的 dto 对象,该对象的类型与相关 dto 相关联:

 private AbstractDto(int id)
 {
      if (id == DtoSelection.Dto1.ToInt()) //extension method I wrote for enums
            return new Dto1();
      if (id == DtoSelection.Dto2.ToInt())
            return new Dto2();
 }

显然我不想这样做 108 次。无论出于何种原因,我的大脑只是缺少一些明显的东西。处理此问题的最佳方法是什么。

【问题讨论】:

  • 我很好奇这是否可能
  • 首先您可以使用switch 改进您的if 列表。你想使用反射吗?
  • 一个枚举中有 108 个不同的 dto 对象背后的原因是什么?为什么不使用数组?
  • 缺少一些脆弱的反射,您可以创建一个Dictionary<DtoSelection, Func<AbstractDto>>,您可以在启动时使用工厂方法填充一次(例如在私有静态构造函数中)。然后就像myDict[(DtoSelection)id]() 一样称呼它。
  • @ChrisSinclair 如何在不“执行 108 次”的情况下填充这本词典?

标签: c# abstraction


【解决方案1】:

使用Activator.CreateInstance 方法并将枚举的ToString 值传递给它。

Type type = Type.GetType(DtoSelection.dto1.ToString());
var temp = Activator.CreateInstance(type);

【讨论】:

  • 我喜欢这个。不过,我必须为每个 dto 执行 108 次。
  • @Robert,您可以对枚举成员进行循环,然后创建实例并将其添加到列表中,循环参见:stackoverflow.com/questions/972307/…
【解决方案2】:

只要 Dto 类与 AbstractDto 定义在同一个命名空间中,这个类就会做你想做的事(如果没有,你需要调整它):

给定以下枚举和类:

public enum DtoSelection
{
    Dto1,
    Dto2,
    Dto3,
}

public abstract class AbstractDto
{
}

public class Dto1 : AbstractDto
{
}

public class Dto2 : AbstractDto
{
}

public class Dto3 : AbstractDto
{
}

此方法将解决它们:

public static class DtoFactory
{
    public static AbstractDto Create(DtoSelection dtoSelection)
    {
        var type = Type.GetType(typeof(AbstractDto).Namespace + "." + dtoSelection.ToString(), throwOnError: false);

        if (type == null)
        {
            throw new InvalidOperationException(dtoSelection.ToString() + " is not a known dto type");
        }

        if (!typeof(AbstractDto).IsAssignableFrom(type))
        {
            throw new InvalidOperationException(type.Name + " does not inherit from AbstractDto");
        }

        return (AbstractDto)Activator.CreateInstance(type);
    }
}

【讨论】:

    【解决方案3】:

    解决这个问题的一种优雅方法是使用属性和一个基类。让我告诉你:

    1. 您必须创建一个基类。在您的示例中,可能是 AbstractDto,如下所示:

       public abstract class AbstractDto : Attribute
       {
            //code of AbstractDto       
       }
      
    2. 然后,我们需要创建一个自定义属性,该属性将用于每个 Dto 类,以确定每个类对应的枚举。

       public class DtoEnumAttribute : Attribute
       {
           public DtoSelection Enum { get; set; }
      
           public DtoEnumAttribute(DtoSelection enum)
           {
               this.Enum = enum;
           }
        }
      
    3. 然后我们应该用适当的枚举来装饰每个子 Dto。让我们为 Dto1 做一个例子:

       [DtoEnum(DtoSelection.Dto1)]
       public class Dto1 : AbstractDto
       {
            //code of Dto1
       }
      
    4. 最后,您可以使用可以接收特定枚举和过滤器或任何您需要的逻辑的方法。以下代码将实例化每个继承自 AbstractDto 的类,这些类由您定义的 Enum 排序。您可以在 Where 子句中使用它来仅返回与所需枚举匹配的类的实例。在这一点上问我你是否需要帮助。

       public void MethodToGetInstances()
       {
              IEnumerable<AbstractDto> dtos = typeof(AbstractDto)
                  .Assembly.GetTypes()
                  .Where(t => t.IsSubclassOf(typeof(AbstractDto)) && !t.IsAbstract)
                  .Select(t => (AbstractDto)Activator.CreateInstance(t))
                  .OrderBy(x => ((DtoEnumAttribute)x.GetType().GetCustomAttributes(typeof(DtoEnumAttribute), false).FirstOrDefault()).Enum);
      
              //If you have parameters on you Dto's, you might pass them to CreateInstance(t, params)
      
       }
      

    dtos 列表中,您将拥有所需的实例。 希望对您有所帮助!

    【讨论】:

      【解决方案4】:

      我会使用函数字典。

      Dictionary<DtoSelection, Func<AbstractDto>> dictionary = 
              new Dictionary<DtoSelection, Func<AbstractDto>>
      {
          {DtoSelection.dto1, () => new dto1()}
      };
      
      var dto = dictionary[DtoSelection.dto1]();
      

      【讨论】:

      • 我试图避免写 108 次。我需要构建所有类型的字典。
      • 对我来说非常有用,避免使用丑陋的switch
      【解决方案5】:

      您应该使用 IoC 容器(Unity、StructureMap、NINject...)

      一个 Ioc 允许:

      • 用名字注册一个类型,像这样(取决于容器):

        Container.Register<AbstractDto,Dto1>(DtoSelection.dto1.ToString());
        
      • 解析类型

        Container.Resolve<AbstractDto>(DtoSelection.dto1.ToString());
        

      这将为您处理实例化的所有细节。

      提供的其他解决方案称为“穷人的 IoC”。不要重新发明轮子。

      当然,你应该把容器隐藏在方法后面:

        public void RegisterDto<TDto>(DtoSelection dtoSelection)
          where TDto : AbstractDto, new()
        {
           Container.Register<AbstractDto,Dto1>(dtoSelection.ToString());
        }
      
      
        public TDto GetDto<TDto>(DtoSelection dtoSelection)
          where TDto : AbstractDto
        {
           return Container.Resolve<AbstractDto>(dtoSelection.ToString()) as TDto;
        }
      

      注意:如果您使用“构造函数注入”,则可以删除 new() 约束 (requirement of parameterless constructor)。构造函数注入允许注册将用作带有参数的构造函数的参数的值。该参数可以是其他对象或抽象对象(接口、抽象类)。为此,您需要在 contianer 中注册此参数。

      无论你选择什么 IoC,都会比“穷人的 IoC”有很多优势。

      更新

      如果你想避免多次写,大部分的IoC CONTAINers也允许按名字注册,所以你可以这样注册:

        // iterate the DtoSelection Enum
        foreach(var e in Enum.GetValues(DtoSelection))
        {
          DtoSelection dtoSel = (DtoSelection)e;
          int n = (int)dtoSel;
          Container.Register<AbstractDto>("Dto" + n, dtoSel.ToString());
        }
      

      注意:第一个参数是类型名称(或完整的类型名称)。第二个是允许解析它的名称。

      【讨论】:

      • 这太过分了,IMO。
      • @newStackExchangeInstance 为什么矫枉过正?您只需添加一个 Nuget 包,并编写几行代码,这比您编写自己的“穷人的 IoC”要少。此外,您还可以避免犯错或忘记细节。它将比您可以实施的几乎任​​何东西更安全、更有效地工作。对我来说,过度杀戮就是重新发明轮子来解决已经解决的问题......并且可能会引入一些意想不到的错误。
      • 让我们看看,哪个更复杂(因此也有问题):Activator.CreateInstance 还是复杂的 IoC 框架?
      • 有些东西不是因为它的复杂性,而是因为它的成熟和质量。大多数 IoC 都设计得很好,有很多单元测试,并且多年来一直在 LoB 应用程序的生产环境中工作。这意味着它不是越野车。使用 Activator.CreateInstance 时会发生什么失败?你如何控制它?你还需要考虑什么?这一切都在成熟的 IoC 容器中解决。不要指望某事不是错误的,因为它是“一条无辜的指令”。在你的代码中,如果用户传递了错误的 id 会发生什么?
      • 它会抛出一个 TypeNotFoundException。 你的会发生什么?
      【解决方案6】:

      尝试使用Activator.CreateInstance

      return (AbstractDto)Activator.CreateInstance
                              (Type.GetType(((DtoSelection)id).ToString(), true, true);
      

      或者,有点作弊,您可以为此使用一些代码生成:

      public static string GenerateValues()
      {
          StringBuilder sb = new StringBuilder();
          sb.AppendLine("DtoSelection selection = (DtoSelection)id;");
          sb.AppendLine("switch (selection)");
          foreach (DtoSelection value in (DtoSelection[])Enum.GetValues(typeof(DtoSelection))
          {
              sb.AppendLine("case DtoSelection." + value.ToString() + ":");
              sb.AppendLine("return new " + value.ToString() + ";");
          }
      }
      

      【讨论】:

        【解决方案7】:
        public AbstractDto CreateDto(DtoSelection selection)
        {
            return (AbstractDto)Activator.CreateInstance(Type.GetType("Perhaps.Some.Qualifier.Here." + selection.ToString()));
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-10-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-19
          • 2012-01-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多