【问题标题】:Factory Creating Objects According to a Generic Type C#工厂根据泛型 C# 创建对象
【发布时间】:2009-07-17 18:05:19
【问题描述】:

根据传递给工厂类的泛型类型实例化对象的最有效方法是什么,例如:

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# generics interface factory


    【解决方案1】:

    我认为最好保持简单,也许是这样的:

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

    您只需为您想要支持的每种类型调用AddLoggerProvider,可以在运行时进行扩展,它确保您明确地将接口的实现添加到库而不是某个对象,这不是很快,因为Activator,但无论如何创建记录器都不会成为瓶颈。希望它看起来不错。

    用法:

    // initialize somewhere
    LoggerFactory.AddLoggerProvider<String, StringLogger>();
    LoggerFactory.AddLoggerProvider<Exception, ExceptionLogger>();
    // etc..
    
    ILogger<string> stringLogger = LoggerFactory.CreateLogger<string>();
    

    注意:每个ILogger&lt;T&gt; 都需要Activator 的无参数构造函数,但这也可以通过add 方法中的new() 泛型约束来确保。

    【讨论】:

    【解决方案2】:

    我想我会这样做:

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

    您付出了可读性的代价(所有这些尖括号,天哪),但正如您所见,它只需要很少的程序逻辑。

    【讨论】:

      【解决方案3】:

      虽然我通常会推荐使用依赖注入框架,但您可以使用反射来实现一些东西,它会在可用类型中搜索实现适当 ILogger 接口的类型。

      我建议您仔细考虑哪些程序集将包含这些记录器实现以及您希望解决方案的可扩展性和防弹性。跨可用程序集和类型执行运行时搜索并不便宜。然而,在这种类型的设计中,这是一种允许可扩展性的简单方法。它还避免了预先配置的问题——但是它要求只有一个具体类型实现 ILogger 接口的特定版本——否则你必须解决一个模棱两可的情况。

      您可能希望执行一些内部缓存以避免在每次调用 Create() 时执行反射的开销。

      这里有一些示例代码,您可以从这里开始。

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

      【讨论】:

      • 它需要 LINQ,C#3.0,我是 2.0,但考虑到你们,我可能会考虑依赖注入框架。
      【解决方案4】:

      取决于您打算处理多少种类型。如果它很小(小于 10),我建议使用 switch 语句,因为它会更快更清晰地阅读。如果你想要更多,你会想要一个查找表(哈希映射、字典等),或者一些基于反射的系统。

      【讨论】:

      • 它最终可能会增长到 10 以上,但是如果你有一个查找表的例子会很有趣。我不太热衷于将类型作为字符串进行比较。
      • Dictionary 可能适合您的需求。
      • 是的,确实,但在 C#2.0 中我不能静态初始化它。私有静态 Dictionary loggerTable = new Dictionnary() { {string, StringLogger} } 不起作用
      • 也许然后在构造函数中初始化?
      【解决方案5】:

      switch 语句与字典 - 与性能无关,因为 switch 被编译成字典。所以实际上这是一个可读性和灵活性的问题。开关更容易阅读,另一方面字典可以在运行时扩展。

      【讨论】:

      • 在运行时扩展工厂的字典会有优势吗?
      • 交换机编译成字典?这听起来很可疑。你是根据什么信息做的?
      • 根据上下文,switch可以编译成dict。我读过一篇关于它的文章,最重要的是,将 CLI 反编译成 C# 可能会很可怕,因为它有多种形式,D​​ict 就是其中之一。
      • 犹大,基于观察反射器中的 IL。
      • 亲爱的,只有这是你的要求
      【解决方案6】:

      您可以考虑在这里使用依赖注入框架,例如 Unity。您可以使用您的因素将返回的通用类型对其进行配置,并在配置中进行映射。 Here's an example of that.

      【讨论】:

      • 考虑过,但我想要一个更简单的解决方案,在代码中。 +1 带来此解决方案。
      • 是的,明白,但为了回答的完整性,您可以在代码中配置 Unity Container。 :)
      【解决方案7】:

      1) 我总是对人们投入日志的复杂性感到惊讶。对我来说似乎总是矫枉过正。如果 log4net 是开源的,我建议你去看看,事实上,你也可以使用它......

      2) 就我个人而言,我尽量避免类型检查——它违背了泛型的意义。只需使用 .ToString() 方法即可。

      【讨论】:

        【解决方案8】:

        Hrm...您实际上可以尝试更聪明一点,这取决于给定的运行时系统支持什么。如果可以的话,我实际上会尽量避免在我的代码中使用任何条件语句,尤其是在多态和动态绑定代码中。那里有一个泛型类,为什么不使用它呢?

        例如,在 Java 中,您可以特别利用已有的静态方法来执行以下操作:

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

        你可以这样称呼它:

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

        这避免了必须有任何条件并且更灵活:任何作为 SomeUsefulClass 的子类(或实现记录器接口,也许)的类都可以返回正确类型的记录器实例。

        【讨论】:

        • 我需要根据可以是任何类型的泛型返回正确类型的记录器。例如,传递给 Factory 的 Exception 将返回一个 ExceptionLogger。它需要在某个地方映射。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多