views:

1656

answers:

8

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...

+2  A: 

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.

C. Ross
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.
Maxime
Dictonary<Type,Type> might suit your needs.
C. Ross
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
Maxime
Initialize in the constructor perhaps then?
C. Ross
+1  A: 

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.

Scott Weinstein
Would there be advantages of extending a Factory's dictionary at runtime?
Maxime
Switch is compiled into a dictionary? That sounds fishy. What information are you basing this on?
Judah Himango
Depending on the context, switch can be compiled into a dict. I read a article about it, and the bottom line was that decompiling CLI into C# can be a horror, as it comes in multiple forms, Dict being one of them.
Dykam
Judah,based on looking at the IL in reflector.
Scott Weinstein
Beaud,Only if this is a requirement of yours
Scott Weinstein
+1  A: 

You might consider using a dependency injection framework here like Unity. You can configure it with the generic types that your factor will return and do the mapping in configuration. Here's an example of that.

JP Alioto
Considered it but I would like a more simple solution, in code. +1 for bringing this solution.
Maxime
Yup, understand, but just for completeness of answer, you can configure a Unity Container in code. :)
JP Alioto
A: 

Hrm... you could actually try to be a little more clever about this, depending on what the given runtime system supported. I actually try to avoid any conditional statements in my code if I can, especially in polymorphic and dynamically bound code. You've got a generic class there, so why not use it?

For example, in Java, you can especially make use of the static method you've got there to do something like this:

public class LoggerFactory<T>
{
    public static ILogger<T> CreateLogger(Class<? extends SomeUsefulClass> aClass);
    {
        // where getLogger() is a class method SomeUsefulClass and its subclasses
        // and has a return value of Logger<aClass>.
        return aClass.getLogger();

        // Or perhaps you meant something like the below, which is also valid.
        // it passes the generic type to the specific class' getLogger() method
        // for correct instantiation. However, be careful; you don't want to get
        // in the habit of using generics as variables. There's a reason they're
        // two different things.

        // return aClass.getLogger(T);
    }
}

You'd call it like this:

public static void main(String[] args)
{
    Logger = LoggerFactory.createLogger(subclassOfUsefulClass.class);
    // And off you go!
}

This avoids having to have any conditionals and is more flexible besides: any class that's a subclass (or implements the logger interface, perhaps) of SomeUsefulClass can return the correctly typed logger instance.

Omar Zakaria
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.
Maxime
+2  A: 

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>(); 
    }
}
LBushkin
It requires LINQ, C#3.0 and I'm 2.0 but I might consider dependency injection framework considering you guys.
Maxime
+2  A: 

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.

Robert Rossney
+1  A: 

1) I'm always amazed at the complexity people put into logging. Always seems like overkill to me. If log4net is opensource, I'd recommend you go look at that, infact, you might just as well use it ...

2) Personally, I try to avoid type checking whenever possible - it defeats the point of generics. Just use the .ToString() method and be done with it.

+1 for point #1
zvolkov
If I have to program, I rather have fun and make good use of OOP ;p. A logging system seems to be perfectly suited for that matter!
Maxime
+6  A: 

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.

kek444
Thanks for the exemple! It looks clean to me. Not too big, just fine.
Maxime