Use an enum to select which class to instantiate

送分小仙女□ 提交于 2019-12-08 19:21:11

问题


I have an enum that I am trying to associate to dto's:

 public enum DtoSelection
 {
     dto1,
     dto2,
     dto3,
 }

There are 108 and values in this enum.

I have a dto object for each of these dto's:

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

I am trying to make a method (eventually a service) that will return me a new dto object of the type associated to the the dto in question:

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

Obviously I do not want to do this 108 times. For whatever reason my brain is just missing something obvious. What is the best way to handle this.


回答1:


This class will do what you want as long as the Dto classes are defined in the same namespace as AbstractDto (you'll need to tweak it if not):

Given the following enums and classes:

public enum DtoSelection
{
    Dto1,
    Dto2,
    Dto3,
}

public abstract class AbstractDto
{
}

public class Dto1 : AbstractDto
{
}

public class Dto2 : AbstractDto
{
}

public class Dto3 : AbstractDto
{
}

This method will resolve them:

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



回答2:


Use Activator.CreateInstance method and pass it enum's ToString value.

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



回答3:


I would use a Dictionary of funcs.

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

var dto = dictionary[DtoSelection.dto1]();



回答4:


Try using Activator.CreateInstance:

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

Or alternatively, a bit of a cheat, you can use some code generation for this:

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() + ";");
    }
}



回答5:


You should use an IoC container (Unity, StructureMap, NINject...)

An Ioc Allows to:

  • Register a Type with name, like so (depends on the container):

    Container.Register<AbstractDto,Dto1>(DtoSelection.dto1.ToString());
    
  • Resolve the Type

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

This will handle all the details of instantiation for you.

The other solutions offered are called "Poor man's IoC". Don't reinvent the wheel.

Of course, you should hide the container behind methods:

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

NOTE: The new() constraint (requirement of parameterless constructor) can be removed if you use "constructor injection". Constructor injection allow to register values that will be used as parameters for constructor with parameters. This parameter can be other objects or abstract objects (interfaces, abstrac classes). For this to work you need to register this parameters in the contianer.

Whatever IoC you choose will have a lot of advantages over the "Poor man's IoC".

UPDATE

If you want to avoid writing it many times, most IoC COntainers also allow to register by name, so you can do the registration like this:

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

NOTE: The first parameter is the type name (or full type name). The second is the name that will allow to resolve it.




回答6:


public AbstractDto CreateDto(DtoSelection selection)
{
    return (AbstractDto)Activator.CreateInstance(Type.GetType("Perhaps.Some.Qualifier.Here." + selection.ToString()));
}


来源:https://stackoverflow.com/questions/17835839/use-an-enum-to-select-which-class-to-instantiate

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!