【问题标题】:Best way to dynamically create classes instead of using a switch block动态创建类而不是使用 switch 块的最佳方法
【发布时间】:2010-08-26 22:22:55
【问题描述】:

目前,我在实现接口 IOutputCacheVaryByCustom 的类中实现了 VaryByCustom 功能

public interface IOutputCacheVaryByCustom
{
    string CacheKey { get; }
    HttpContext Context { get; }
}

实现此接口的类有一些约定,类的名称将是“OutputCacheVaryBy_______”,其中空白是从页面上的 varyByCustom 属性传入的值。另一个约定是 Context 将通过构造函数注入来设置。

目前我基于一个枚举和一个类似于

的 switch 语句
public override string GetVaryByCustomString(HttpContext context, 
                                              string varyByCustomTypeArg)
{
    //for a POST request (postback) force to return back a non cached output
    if (context.Request.RequestType.Equals("POST"))
    {
        return "post" + DateTime.Now.Ticks;
    }
    var varyByCustomType = EnumerationParser.Parse<VaryByCustomType?>
                            (varyByCustomTypeArg).GetValueOrDefault();


    IOutputCacheVaryByCustom varyByCustom;
    switch (varyByCustomType)
    {
        case VaryByCustomType.IsAuthenticated:
            varyByCustom = new OutputCacheVaryByIsAuthenticated(context);
            break;
        case VaryByCustomType.Roles:
            varyByCustom = new OutputCacheVaryByRoles(context);
            break;
        default:
            throw new ArgumentOutOfRangeException("varyByCustomTypeArg");
    }

    return context.Request.Url.Scheme + varyByCustom.CacheKey;
}

由于我一直都知道该类将是 OutputCacheVaryBy + varyByCustomTypeArg 并且唯一的构造函数参数将是 context 我意识到我可以绕过需要这个美化的 if else 块并且可以使用 Activator 实例化我自己的对象。

话虽如此,反射并不是我的强项,我知道Activator 与静态创建和其他生成对象的方式相比要慢得多。有什么理由我应该坚持使用当前的代码还是应该使用Activator 或类似的方式来创建我的对象?

我看过博客 http://www.smelser.net/blog/post/2010/03/05/When-Activator-is-just-to-slow.aspx,但我不确定这将如何应用,因为我在运行时使用类型而不是静态 T。

【问题讨论】:

  • 创建对象是否昂贵(耗时)?构建过程中是否需要上下文,是否可以改为通过 Context 属性设置?这些问题的答案是提供最佳解决方案所必需的。
  • 没有创建微不足道的对象,并且最好在构造函数中设置上下文,因为这是对类的核心依赖,但是它可以在属性上公开和设置,但这为 NRE 留出了空间。
  • 你用的是什么版本的c#?

标签: c# reflection dynamic-class-creation


【解决方案1】:

如果反射对你来说太慢了。您可能可以让自己的 ObjectFactory 工作。 这真的很容易。只需向您的界面添加一个新方法即可。

    public interface IOutputCacheVaryByCustom
    {
        string CacheKey { get; }
        IOutputCacheVaryByCustom NewObject();
    }

创建一个包含对象模板的静态只读 CloneDictionary。

    static readonly
        Dictionary<VaryByCustomType, IOutputCacheVaryByCustom> cloneDictionary
        = new Dictionary<VaryByCustomType, IOutputCacheVaryByCustom>
        {
            {VaryByCustomType.IsAuthenticated, new OutputCacheVaryByIsAuthenticated{}},
            {VaryByCustomType.Roles, new OutputCacheVaryByRoles{}},
        };

如果你完成了,你可以使用你已经拥有的枚举来选择字典中的模板并调用 NewObject()

        IOutputCacheVaryByCustom result = 
             cloneDictionary[VaryByCustomType.IsAuthenticated].NewObject();

就这么简单。您必须实现的 NewObject() 方法将通过直接创建对象返回一个新的实例。

    public class OutputCacheVaryByIsAuthenticated: IOutputCacheVaryByCustom
    {
        public IOutputCacheVaryByCustom NewObject() 
        {
            return new OutputCacheVaryByIsAuthenticated(); 
        }
    }

这就是你所需要的。而且速度快得令人难以置信。

【讨论】:

    【解决方案2】:

    您实际上不需要使用反射,因为它是一组相当有限的可能值。但是你可以做这样的事情

    internal class Factory<T,Arg>
    {
       Dictionary<string,Func<Arg.T>> _creators;
       public Factory(IDictionary<string,Func<Arg,T>> creators)
      {
         _creators = creators;
      }
    }
    

    并将您的创建逻辑替换为

    _factory[varyByCustomTypeArg](context);
    

    它没有开关那么快,但它可以保持构造和很好地分开使用

    【讨论】:

      【解决方案3】:

      我真的很喜欢让其他人负责创建对象。例如,如果我需要一个接口的不同具体实现,IoC 容器就为我创造了奇迹。

      作为一个使用统一的简单示例,您有一个配置部分将键链接到如下实现:

      public void Register(IUnityContainer container)
      {
         container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByIsAuthenticated>("auth");
         container.RegisterType<IOutputCacheVaryByCustom,OutputCacheVaryByRoles>("roles");
      }
      

      你的创作看起来会像这样简单得多:

      //injected in some form
      private readonly IUnityContainer _container;
      
      public override string GetVaryByCustomString(HttpContext context, 
                                                    string varyByCustomTypeArg)
      {
          //for a POST request (postback) force to return back a non cached output
          if (context.Request.RequestType.Equals("POST"))
          {
              return "post" + DateTime.Now.Ticks;
          }
          try
          {
          IOutputCacheVaryByCustom varyByCustom = _container.Resolve<IOutputCacheVaryByCustom>(varyByCustomTypeArg, new DependencyOverride<HttpContext>(context));
          }
          catch(Exception exc)
          {
             throw new ArgumentOutOfRangeException("varyByCustomTypeArg", exc);
          }
          return context.Request.Url.Scheme + varyByCustom.CacheKey;
      }
      

      或者,如果 IoC 不是一个选项,我会让工厂创建具体的类,所以你永远不必担心你的实际方法。

      【讨论】:

        【解决方案4】:

        继续使用 switch 语句。如果你只有几个这样的可能情况,那么你只是在尝试使用巧妙的抽象来避免坐下来让程序的困难部分工作......

        也就是说,从您的问题看来,使用 Activator 可能对您有用。你测试过吗?真的太慢了​​吗?

        或者,您可以在Dictionary&lt;string, Func&lt;IOutputCacheVaryByCustom&gt; 中保留一堆工厂方法。如果您经常(在循环中)创建这些对象,我会使用它。然后,您还可以将 string 键优化为您的 enum 并完成转换。更抽象只会隐藏这段代码的意图......

        【讨论】:

          【解决方案5】:

          这里是一个创建新对象的例子

          public static object OBJRet(Type vClasseType)
          {
              return typeof(cFunctions).GetMethod("ObjectReturner2").MakeGenericMethod(vClasseType).Invoke(null, new object[] { });
          }
          
          public static object ObjectReturner2<T>() where T : new()
          {
              return new T();
          }
          

          一些信息:

          • cFunctions 是包含函数的静态类的名称

          还有一个例子,我将类包含在一个数组列表中:

              public static object OBJRet(Type vClasseType, ArrayList tArray, int vIndex)
              {
                  return typeof(cFunctions).GetMethod("ObjectReturner").MakeGenericMethod(vClasseType).Invoke(null, new object[] { tArray, vIndex });
              }
          
              public static object ObjectReturner<T>(ArrayList tArray, int vIndex) where T : new()
              {
                  return tArray[vIndex];
              }
          

          【讨论】:

          • 这就是我曾经能够将不同的类包含到需要类似转换的数组列表中。这些是静态类/方法,但它们也可以是正常的,它们是静态的只是因为它是用作 dll 的通用类。我相信你会弄清楚如何根据你的需要调整它。
          【解决方案6】:

          使用反射。

              public override string GetVaryByCustomString(HttpContext context,   
                                                    string varyByCustomTypeArg)
              {
                  //for a POST request (postback) force to return back a non cached output   
                  if (context.Request.RequestType.Equals("POST"))   
                  {   
                      return "post" + DateTime.Now.Ticks;   
                  }
          
                  Type type = Type.GetType("OutputCacheVaryBy" + varyByCustomTypeArg, false)
                  if (type == null)
                  {
                      Console.WriteLine("Failed to find a cache of type " + varyByCustomTypeArg);
                      return null;
                  }
          
                  var cache = (IOutputCacheVaryByCustom)Activator.CreateInstance(type, new object[]{context});
                  return context.Request.Url.Scheme + cache.CacheKey;
              } 
          

          您可能必须在 typename 前面加上命名空间:"My.Name.Space.OutputCacheVaryBy"。如果这不起作用,请尝试使用程序集限定名称:

          Type.GetType("Name.Space.OutputCacheVaryBy" + varyByCustomTypeArg + ", AssemblyName", false)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-06-26
            • 2013-01-12
            • 2015-06-14
            • 2011-06-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多