10

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.

Robert
  • 4,306
  • 11
  • 45
  • 95
  • i'm curious if this is possible – Jonesopolis Jul 24 '13 at 13:46
  • First you can improve your `if` list by using `switch`. And do you want to use reflection? – Cédric Bignon Jul 24 '13 at 13:46
  • 1
    What is the reasoning behind having 108 different dto objects in an enum? why not use an array? – chancea Jul 24 '13 at 13:47
  • Short of some brittle reflection, you could create a `Dictionary>` which you can populate with factory methods once on startup (say in a private static constructor). Then just call it like `myDict[(DtoSelection)id]()`. – Chris Sinclair Jul 24 '13 at 13:47
  • @chancea They 108 dto objects refer to specific reports that I am running. They actually have methods and other things based on the report. I have it working making a class for each of them but I really want to select them using the enum or something similar. – Robert Jul 24 '13 at 13:48
  • there are so many ways to do it, proto-type DP for example would work for that – No Idea For Name Jul 24 '13 at 13:48
  • 1
    @ChrisSinclair How do you populate this dictionary without "doing this 108 times"? – Cédric Bignon Jul 24 '13 at 13:50
  • @CédricBignon Reflection might be what I need to use. – Robert Jul 24 '13 at 13:50
  • @CédricBignon: I still suspect you have to do it, but building a factory set (even if it's for 108 items) once in-code I don't see as a big issue. The _has_ to be wired somehow and I'll take in-code wiring over brittle reflection any day. You could even do it by defining attributes on the `enum` type targeting the expected DTO type. Point is, the "best way" for Robert to handle this, in my opinion, is not by building type references by concatenating string/value representations of the `DtoSelection` enum. – Chris Sinclair Jul 24 '13 at 14:17

7 Answers7

5

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

Type type = Type.GetType(DtoSelection.dto1.ToString());
var temp = Activator.CreateInstance(type);
Habib
  • 219,104
  • 29
  • 407
  • 436
  • I like this. I would have to do this 108 times for each dto though. – Robert Jul 24 '13 at 13:59
  • @Robert, you can have a loop over your enum's members and then create instance and add it in a list, for looping see: http://stackoverflow.com/questions/972307/can-you-loop-through-all-enum-values – Habib Jul 24 '13 at 14:22
5

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);
    }
}
Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
2

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]();
Tim B
  • 2,340
  • 15
  • 21
2

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.

JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • 1
    This is overkill, IMO. – It'sNotALie. Jul 24 '13 at 14:42
  • @newStackExchangeInstance Why overkill? You simple have to add a Nuget package, and write a few lines of code, less than if you write you own "poor man's IoC". Besides you avoid making mistakes or forgetting nitty-gritty details. It will work more safely and efficiently that nearly anything you can implement. For me overkilling is reinventing the wheel to solve a problem already solved... and probably introducing some unexpected bug. – JotaBe Jul 24 '13 at 14:50
  • Let's see, what's more complex (and therefore buggy): Activator.CreateInstance or a complex IoC framework? – It'sNotALie. Jul 24 '13 at 14:58
  • Something is not buggy because of its complexity, but because of its maturity and quality. Most IoC are well designed, have lots of unit tests and have been working in production in LoB apps for years. That means it's not buggy. What can fail when you use Activator.CreateInstance? How do you control it? What else do you have to take into account? This all is solved in mature IoC containers. Don't expect that something is not buggy because is "a single innocent instruction". In you code, what happens if the user passes the wrong id? – JotaBe Jul 24 '13 at 15:20
  • It throws a TypeNotFoundException. What happens in **yours?** – It'sNotALie. Jul 24 '13 at 15:27
  • It would give extra information as "the type AbstractDto with name Dto545 could not be resolved". More informative. Anyway, we're not going to convince each other :) – JotaBe Jul 24 '13 at 15:48
2

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!

Mauro Bilotti
  • 5,628
  • 4
  • 44
  • 65
1

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() + ";");
    }
}
It'sNotALie.
  • 22,289
  • 12
  • 68
  • 103
0
public AbstractDto CreateDto(DtoSelection selection)
{
    return (AbstractDto)Activator.CreateInstance(Type.GetType("Perhaps.Some.Qualifier.Here." + selection.ToString()));
}
lightbricko
  • 2,649
  • 15
  • 21