Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use an enum to select which class to instantiate

Tags:

c#

abstraction

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.

like image 593
Robert Avatar asked Jul 24 '13 13:07

Robert


2 Answers

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.

like image 97
JotaBe Avatar answered Oct 21 '22 16:10

JotaBe


An elegant way of solving this is by using Attributes and one base class. Let me show you:

  1. You must create a base class. In your example, could be AbstractDto, like following:

     public abstract class AbstractDto : Attribute
     {
          //code of AbstractDto       
     }
    
  2. Then, we need to create a custom attribute that will be used on every Dto class to determine which enum corresponds to each class.

     public class DtoEnumAttribute : Attribute
     {
         public DtoSelection Enum { get; set; }
    
         public DtoEnumAttribute(DtoSelection enum)
         {
             this.Enum = enum;
         }
      }
    
  3. Then we should decorate every child Dto with its proper enum. Let's do an example for Dto1:

     [DtoEnum(DtoSelection.Dto1)]
     public class Dto1 : AbstractDto
     {
          //code of Dto1
     }
    
  4. Finally, you can use a method that can receive an specific enum and filter, or whatever logic you need. The following code will instantiate every class that inherit from AbstractDto ordered by the Enum that you have defined. You can use it on a Where clause to return only the instance of the class that matches the enum that you want. Ask me if you need help on this point.

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

On the dtos list, you will have the instances that you want. Hope it helps!

like image 39
Mauro Bilotti Avatar answered Oct 21 '22 18:10

Mauro Bilotti