Factory Creating Objects According to a Generic Type C#

14,307

Solution 1

I think it's best to keep it simple, perhaps something like this:

public static class LoggerFactory
{
    static readonly Dictionary<Type, Type> loggers = new Dictionary<Type, Type>();

    public static void AddLoggerProvider<T, TLogger>() where TLogger : ILogger<T>, new()
    {
        loggers.Add(typeof(T), typeof(TLogger));
    }

    public static ILogger<T> CreateLogger<T>()
    {
        //implement some error checking here
        Type tLogger = loggers[typeof(T)];

        ILogger<T> logger = (ILogger<T>) Activator.CreateInstance(tLogger);

        return logger;
    }
}

You just call the AddLoggerProvider for each type you want to support, can be extended at runtime, it ensures you definetly add an implementation of the interface to the library and not some object, isn't very fast because of the Activator, but creating a logger wont likely be a bottleneck anyway. Hope it looks okay.

Usage:

// initialize somewhere
LoggerFactory.AddLoggerProvider<String, StringLogger>();
LoggerFactory.AddLoggerProvider<Exception, ExceptionLogger>();
// etc..

ILogger<string> stringLogger = LoggerFactory.CreateLogger<string>();

Note: each ILogger<T> requires a parameterless constructor for the Activator, but that too is ensured with the new() generic constraint in the add method.

Solution 2

I think I'd do it like this:

public class LoggerFactory<T>
{
    private static Dictionary<Type, Func<ILogger<T>>> LoggerMap = 
        new Dictionary<Type, Func<ILogger<T>>>
    {
        { typeof(string), 
            () => new StringILogger() as ILogger<T> },
        { typeof(StringWriter), 
            () => new StringWriterILogger() as ILogger<T> }
    };

    public static ILogger<T> CreateLogger()
    {
        return LoggerMap[typeof(T)]();
    }
}

You pay something of a readability price (all those angle brackets, sheesh), but as you can see it makes for very little program logic.

Solution 3

Although I typically would recommend using a dependency injection framework, you could implement something with reflection that would search the available types for one that implements the appropriate ILogger interface.

I would suggest that you carefully consider which assemblies will contain these logger implementations and how extensible and bullet-proof you want the solution to be. Performing runtime searches across the available assemblies and types is not inexpensive. It is, however, an easy way to allow extensibility in this type of design. It also avoid the issue of up-front configuration - however it requires that only a single concrete type implement a particular version of the ILogger<> interface - otherwise there's an ambiguous situation you have to resolve.

You may want to perform some internal caching to avoid the expense of performing reflection on each call to Create().

Here is some sample code you could start with.

using System;
using System.Linq;
using System.Reflection;

public interface ILogger<T> { /*... */}

public class IntLogger : ILogger<int> { }

public class StringLogger : ILogger<string> { }

public class DateTimeLogger : ILogger<DateTime> { }

public class LoggerFactory
{
    public static ILogger<T> Create<T>()
    {
        // look within the current assembly for matching implementation
        // this could be extended to search across all loaded assemblies
        // relatively easily - at the expense of performance
        // also, you probably want to cache these results...
        var loggerType = Assembly.GetExecutingAssembly()
                     .GetTypes()
                     // find implementations of ILogger<T> that match on T
                     .Where(t => typeof(ILogger<T>).IsAssignableFrom(t))
                     // throw an exception if more than one handler found,
                     // could be revised to be more friendly, or make a choice
                     // amongst multiple available options...
                     .Single(); 

        /* if you don't have LINQ, and need C# 2.0 compatibility, you can use this:
        Type loggerType;
        Type[] allTypes = Assembly.GetExecutingAssembly().GetTypes();
        foreach( var type in allTypes )
        {
            if( typeof(ILogger<T>).IsAssignableFrom(type) && loggerType == null )
                loggerType = type;
            else
                throw new ApplicationException( "Multiple types handle ILogger<" + typeof(T).Name + ">" );                   
        }

        */

        MethodInfo ctor = loggerType.GetConstructor( Type.EmptyTypes );
        if (ctor != null)
            return ctor.Invoke( null ) as ILogger<T>;

        // couldn't find an implementation
        throw new ArgumentException(
          "No mplementation of ILogger<{0}>" + typeof( T ) );
    }
}

// some very basic tests to validate the approach...
public static class TypeDispatch
{
    public static void Main( string[] args )
    {
        var intLogger      = LoggerFactory.Create<int>();
        var stringLogger   = LoggerFactory.Create<string>();
        var dateTimeLogger = LoggerFactory.Create<DateTime>();
        // no logger for this type; throws exception...
        var notFoundLogger = LoggerFactory.Create<double>(); 
    }
}

Solution 4

Depends on how many types you intend to handle. If it's small (less than 10) I'd suggest a switch statement, as it'll be fast and cleaner to read. If you want more you would want a lookup table (Hash Map, Dictionary, etc), or some reflection based system.

Solution 5

switch statement vs dictionary - doesn't matter for perfomance, as a switch is compiled into a dictionary. So really it's a matter of readabilty and flexibility. The switch is easier to read, on the other hand a dictionary can be extended at runtime.

Share:
14,307
maxbeaudoin
Author by

maxbeaudoin

I'm an entrepreneur, software architect and machine learning specialist.

Updated on June 28, 2022

Comments

  • maxbeaudoin
    maxbeaudoin almost 2 years

    What would be the most efficient way to instanciate an object according to a generic type passed to a Factory class, for instance:

    public class LoggerFactory
    {
        public static ILogger<T> Create<T>()
        {
            // Switch Statement?
            // Generic Dictionary?
            // EX.: if "T" is of type "string": return (ILogger<T>)new StringLogger();
        }
    }
    

    How would you do it? Which branching statement? etc...

  • maxbeaudoin
    maxbeaudoin almost 15 years
    It will probably grow above 10 eventually, but a lookup table exemple would be interesting if you have any. I'm not too enthusiastic about comparing types as string.
  • maxbeaudoin
    maxbeaudoin almost 15 years
    Would there be advantages of extending a Factory's dictionary at runtime?
  • Judah Gabriel Himango
    Judah Gabriel Himango almost 15 years
    Switch is compiled into a dictionary? That sounds fishy. What information are you basing this on?
  • maxbeaudoin
    maxbeaudoin almost 15 years
    Considered it but I would like a more simple solution, in code. +1 for bringing this solution.
  • C. Ross
    C. Ross almost 15 years
    Dictonary<Type,Type> might suit your needs.
  • maxbeaudoin
    maxbeaudoin almost 15 years
    I need to return the correctly typed logger according to a Generic that can be of any Type. For instance, an Exception passed to the Factory would return an ExceptionLogger. It needs mapping somewhere.
  • maxbeaudoin
    maxbeaudoin almost 15 years
    Yes, indeed but in C#2.0 I can't initialize it statically. private static Dictionary<Type, Type> loggerTable = new Dictionnary<Type, Type>() { {string, StringLogger} } Won't work
  • Scott Weinstein
    Scott Weinstein almost 15 years
    Judah, based on looking at the IL in reflector.
  • Scott Weinstein
    Scott Weinstein almost 15 years
    Beaud, Only if this is a requirement of yours
  • maxbeaudoin
    maxbeaudoin almost 15 years
    It requires LINQ, C#3.0 and I'm 2.0 but I might consider dependency injection framework considering you guys.
  • C. Ross
    C. Ross almost 15 years
    Initialize in the constructor perhaps then?
  • Prisoner ZERO
    Prisoner ZERO over 12 years
    Actually, you will find using the Activator is very slow: csharp-architect.com/post/2009/06/11/…
  • Kenan E. K.
    Kenan E. K. over 12 years
    I wrote "isn't very fast because of the Activator" so that isn't exclusive with "is very slow" :)