Factory Creating Objects According to a Generic Type C#
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.
maxbeaudoin
I'm an entrepreneur, software architect and machine learning specialist.
Updated on June 28, 2022Comments
-
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 almost 15 yearsIt 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 almost 15 yearsWould there be advantages of extending a Factory's dictionary at runtime?
-
Judah Gabriel Himango almost 15 yearsSwitch is compiled into a dictionary? That sounds fishy. What information are you basing this on?
-
maxbeaudoin almost 15 yearsConsidered it but I would like a more simple solution, in code. +1 for bringing this solution.
-
C. Ross almost 15 yearsDictonary<Type,Type> might suit your needs.
-
maxbeaudoin almost 15 yearsI 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 almost 15 yearsYes, 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 almost 15 yearsJudah, based on looking at the IL in reflector.
-
Scott Weinstein almost 15 yearsBeaud, Only if this is a requirement of yours
-
maxbeaudoin almost 15 yearsIt requires LINQ, C#3.0 and I'm 2.0 but I might consider dependency injection framework considering you guys.
-
C. Ross almost 15 yearsInitialize in the constructor perhaps then?
-
Prisoner ZERO over 12 yearsActually, you will find using the Activator is very slow: csharp-architect.com/post/2009/06/11/…
-
Kenan E. K. over 12 yearsI wrote "isn't very fast because of the Activator" so that isn't exclusive with "is very slow" :)