2009-07-17 18 views
7

lo que sería la forma más eficiente para instanciar un objeto de acuerdo con un tipo genérico pasado a una clase de fábrica, por ejemplo:fábrica Creación de objetos de acuerdo con un tipo genérico de C#

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

¿Cómo hacerlo? ¿Cuál declaración de bifurcación? etc ...

Respuesta

18

creo que lo mejor es que sea sencillo, tal vez algo como esto:

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

que acaba de llamar la AddLoggerProvider para cada tipo que desea apoyar, se puede ampliar en tiempo de ejecución, se garantiza que definitivamente añade una la implementación de la interfaz para la biblioteca y no algún objeto, no es muy rápida debido al Activator, pero la creación de un registrador probablemente no sea un cuello de botella de todos modos. Espero que se vea bien.

Uso:

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

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

Nota: cada ILogger<T> requiere un constructor sin parámetros para la Activator, pero eso también está garantizado con el new() limitación genérica en el método add.

+1

En realidad, el uso del Activador es muy lento: http://www.csharp-architect.com/post/2009/06/11/Generic-new-T%28%29- Performance-Issue-and-a-Solution.aspx –

+1

Escribí "no es muy rápido debido al Activador", por lo que no es exclusivo con "es muy lento" :) –

2

Depende de la cantidad de tipos que pretenda manipular. Si es pequeño (menos de 10), sugeriría una declaración de cambio, ya que será más rápido y más limpio de leer. Si quieres más, querrías una tabla de búsqueda (Hash Map, Dictionary, etc.) o algún sistema basado en la reflexión.

+0

Probablemente crecerá por encima de 10 con el tiempo, pero un ejemplo de tabla de búsqueda sería interesante si tiene alguno. No estoy muy entusiasmado con la comparación de tipos como cadena. – maxbeaudoin

+0

Dictonary puede satisfacer sus necesidades. –

+0

Sí, de hecho, pero en C# 2.0 no puedo inicializarlo estáticamente. Diccionario estática privada loggerTable = new Dictionnary () {{string, StringLogger}} no funcionará – maxbeaudoin

1

instrucción de conmutación vs diccionario: no importa para el rendimiento, ya que un conmutador se compila en un diccionario. Entonces, realmente se trata de una cuestión de flexibilidad y flexibilidad. El interruptor es más fácil de leer, por otro lado, un diccionario se puede extender en tiempo de ejecución.

+0

¿Sería conveniente ampliar el diccionario de fábrica en tiempo de ejecución? – maxbeaudoin

+0

¿El interruptor está compilado en un diccionario? Eso suena sospechoso. ¿En qué información estás basando esto? –

+0

Según el contexto, el conmutador se puede compilar en un dict. Leí un artículo al respecto, y la conclusión es que descompilar CLI en C# puede ser un horror, ya que viene en múltiples formas, Dict es uno de ellos. – Dykam

1

Puede considerar el uso de un marco de inyección de dependencia aquí como Unity. Puede configurarlo con los tipos genéricos que devolverá su factor y hacer la asignación en la configuración. Here's an example of that.

+0

Lo consideré pero me gustaría una solución más simple, en código. +1 por traer esta solución. – maxbeaudoin

+0

Sí, entienda, pero solo para completar la respuesta, puede configurar un Contenedor de unidades en el código. :) –

0

Hrm ... en realidad podría tratar de ser un poco más inteligente sobre esto, dependiendo de lo que sea compatible con el sistema de tiempo de ejecución dado. De hecho, trato de evitar cualquier declaración condicional en mi código si puedo, especialmente en código polimórfico y dinámicamente ligado. Tienes una clase genérica allí, entonces ¿por qué no usarla?

Por ejemplo, en Java, se puede hacer uso de todo el método estático que tienes ahí a hacer algo como esto:

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

que se podría llamar así:

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

Esto evita tener que tener condicionales y es más flexible además: cualquier clase que sea una subclase (o implemente la interfaz del registrador, quizás) de SomeUsefulClass puede devolver la instancia del registrador correctamente tipada.

+0

Necesito devolver el registrador tipeado correctamente de acuerdo con un genérico que puede ser de cualquier tipo. Por ejemplo, una excepción pasada a la fábrica devolvería un ExceptionLogger. Necesita un mapa en alguna parte. – maxbeaudoin

4

Aunque normalmente recomendaría usar un marco de inyección de dependencia, podría implementar algo con reflexión que buscaría los tipos disponibles para uno que implemente la interfaz de ILogger apropiada.

Le sugiero que considere cuidadosamente qué ensamblajes contendrán estas implementaciones de registrador y qué tan extensible y a prueba de balas quiere que sea la solución. Realizar búsquedas de tiempo de ejecución en los ensamblados y tipos disponibles no es barato. Sin embargo, es una forma fácil de permitir la extensibilidad en este tipo de diseño. También evita el problema de la configuración inicial; sin embargo, requiere que solo un único tipo de concreto implemente una versión particular de la interfaz ILogger <>; de lo contrario, hay una situación ambigua que debe resolver.

Es posible que desee realizar un almacenamiento en caché interno para evitar el gasto de realizar la reflexión en cada llamada a Create().

Aquí hay un código de muestra con el que puede comenzar.

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>(); 
    } 
} 
+0

Requiere LINQ, C# 3.0 y soy 2.0 pero podría considerar el marco de inyección de dependencias considerándolos chicos. – maxbeaudoin

6

creo que lo haría así:

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)](); 
    } 
} 

Usted paga algo de un precio de legibilidad (todos los paréntesis angulares, UH), pero como se puede ver que lo convierte en muy poco lógica de programa.

1

1) Siempre me sorprende la complejidad que la gente pone en el registro. Siempre me parece exagerado. Si log4net es de código abierto, te recomiendo que veas eso, de hecho, es mejor que lo uses ...

2) Personalmente, trato de evitar la verificación de tipos siempre que sea posible - se derrota el punto de los genéricos . Solo use el método .ToString() y termine con esto.

+0

+1 para el punto n. ° 1 – zvolkov

Cuestiones relacionadas