【问题标题】:Best perfomance option to instantiate class from string从字符串实例化类的最佳性能选项
【发布时间】:2016-03-14 18:39:34
【问题描述】:

我在这里发现了一些关于 SO 的问题,这些问题展示了几种从字符串实例化类的方法,我发现的唯一一种方法是 Activator.CreateInstance。知道这不是最快的,我试图找到其他东西并找到Compiled Expressions

现在,我如何实现Compiled Expression 来实例化一个基于给定字符串作为其类型的新类?有可能吗?

这是我的代码:

public List<HtmlBlock> ParseBlocks( Page page, ControllerContext controller )
{
    var nodeBlocks = GetNodes( page.html );
    var blocks = new List<HtmlBlock>();

    Parallel.ForEach( nodeBlocks, block => blocks.Add( ParseNode( block, controller ) ) );

    return blocks;
}

private static HtmlBlock ParseNode( HtmlBlock block, ControllerContext controller )
{
    try
    {
        //Instantiate the class
        var type = Activator.CreateInstance( null, "Site.ViewModels." + block.Type );

        //Populate selected template
        block.SetHtml( new HelperController().RenderView( block.Template, type.Unwrap(), controller ) );

        return block;
    }
    //Suppress any error since we just want to hide the block on parse error
    catch (Exception)
    {
        block.SetHtml( "" );

        return block;
    }
}

只是为了提供一些上下文,我正在尝试创建一个自定义模板构建器,用户可以输入这样的 HTML 标记:

<template dir="_Courses" where="active=1" order="name" type="CoursesViewModel"></template>

我将使用我的数据库中的数据呈现选定的模板。我需要用 4 个参数实例化 CoursesViewModelstring where, string select, string order, int take,这些是我的查询过滤参数。

OBS:我也尝试过使用FastActivator,但要使用它,我还必须使用 `Type.GetType("Site.ViewModels." + block.Type) 并且我认为它最终会像我的其他选择一样昂贵,对吗?

编辑 1

我已经使用我的 MVC 应用程序执行了两个测试并应用了 3 种不同的方法,结果以毫秒为单位,并且使用了 20k 次迭代。第三个是使用switch/case来寻找正确的类的方法

1) ViewModelFactory.CreateInstance("NameSpace.ClassName", "", "", "", 0)
2) Activator.CreateInstance(null, "NameSpace.ClassName")
3) HtmlParser.GetClassType("ClassName")

------------------------------------------------------------------------
   1st Test   2nd Test | 20k
1) 93068    | 110499
2) 117460   | 89995
3) 82866    | 77477

我使用PasteBin 来分享代码。奇怪的是,这些方法在每种情况下的工作方式都不同,第一次执行时@Ivan Stoev 是最慢的代码,但在页面刷新时,他的代码工作得更好,而我的 switch/case 是最快的。谁能解释一下这是为什么?

编辑 2

这些测试实现了 Ivan Stoev 代码的更改版本,其中 Dictionary 更改为 ConcurrentDictionary 并且 Activator 是通过参数实现的

1) ViewModelFactory.CreateInstance( "ClassName", "", "", "", 0 )

2) var type = Type.GetType( "NameSpace.ClassName" );
   var obj = Activator.CreateInstance( type, new object[] { "", "", "", 0 } );

3) HtmlParser.GetClassType("ClassName")

------------------------------------------------------------------------
   1st Test   2nd Test | 200k
1) 3418     | 3674
2) 5759     | 5859
3) 3776     | 4117

这里是修改后代码的 bin:PasteBin

【问题讨论】:

  • 我不知道它是否会变得更快。无论如何,您都需要使用某种形式的反射。除非...您有固定数量的类并且直接实例化。
  • @JeffMercado,直接实例化是什么意思?我不能使用Compiled Expressions
  • 就像做这样的事情:if (block.Type == "SomeType") return new SomeType();
  • 所以你想通过名称来实例化类,它有 4 个参数 string where, string select, string order, int take 的构造函数,对吗?还是只是无参数构造函数?
  • 嗯,你需要测量。但是这个选项是存在的。但我不确定我之前的评论中你想要什么类型的构造函数:(A) 还是 (B)?

标签: c# linq


【解决方案1】:

因为 Ivan 已经编写了基本代码,所以我将发布基于表达式树的等效代码。它可能在生成甚至执行时都比较慢。

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

public static class ViewModelFactory
{
    static readonly Type[] arguments = { typeof(string), typeof(string), typeof(string), typeof(int) };

    static readonly ConcurrentDictionary<string, Func<string, string, string, int, object>> factoryCache = new ConcurrentDictionary<string, Func<string, string, string, int, object>>();

    public static object CreateInstance(string typeName, string where, string select, string order, int take)
    {
        Func<string, string, string, int, object> factory;

        lock (factoryCache)
        {
            if (!factoryCache.TryGetValue(typeName, out factory))
            {
                var type = Type.GetType(typeName);
                var ci = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, arguments, null);

                ParameterExpression par1 = Expression.Parameter(typeof(string), "par1");
                ParameterExpression par2 = Expression.Parameter(typeof(string), "par2");
                ParameterExpression par3 = Expression.Parameter(typeof(string), "par3");
                ParameterExpression par4 = Expression.Parameter(typeof(int), "par4");

                var exprNew = Expression.New(ci, par1, par2, par3, par4);

                var lambda = Expression.Lambda<Func<string, string, string, int, object>>(exprNew, par1, par2, par3, par4);
                factory = lambda.Compile();
                factoryCache.Add(typeName, factory);
            }
        }
        return factory(where, select, order, take);
    }
}

【讨论】:

  • 感谢您为该问题提供其他选择
  • 是的,你们两个。我想知道谁投了反对票,并且没有对原因留下建设性的解释。如果您不必解释自己,那么投票很容易
【解决方案2】:

您可以使用以下帮助类,但您需要自己衡量性能:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

public static class ViewModelFactory
{
    static readonly Type[] arguments = { typeof(string), typeof(string), typeof(string), typeof(int) };

    static readonly Dictionary<string, Func<string, string, string, int, object>>
    factoryCache = new Dictionary<string, Func<string, string, string, int, object>>();

    public static object CreateInstance(string typeName, string where, string select, string order, int take)
    {
        Func<string, string, string, int, object> factory;
        lock (factoryCache)
        {
            if (!factoryCache.TryGetValue(typeName, out factory))
            {
                var type = Type.GetType(typeName);
                var ci = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, arguments, null);
                var dm = new DynamicMethod("Create" + typeName, type, arguments, true);
                var il = dm.GetILGenerator();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldarg_2);
                il.Emit(OpCodes.Ldarg_3);
                il.Emit(OpCodes.Newobj, ci);
                il.Emit(OpCodes.Ret);
                factory = (Func<string, string, string, int, object>)dm.CreateDelegate(
                    typeof(Func<string, string, string, int, object>));
                factoryCache.Add(typeName, factory);
            }
        }
        return factory(where, select, order, take);
    }
}

更新:正如@xanatos 正确提到的,可以通过将DictionaryMonitor 锁替换为ConcurrentDictionary 来改进上述内容:

using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection.Emit;

public static class ViewModelFactory
{
    static readonly Type[] arguments = { typeof(string), typeof(string), typeof(string), typeof(int) };

    static readonly ConcurrentDictionary<string, Func<string, string, string, int, object>>
    factoryCache = new ConcurrentDictionary<string, Func<string, string, string, int, object>>();

    static readonly Func<string, Func<string, string, string, int, object>> CreateFactory = typeName =>
    {
        var type = Type.GetType(typeName);
        var ci = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, arguments, null);
        var dm = new DynamicMethod("Create" + typeName, type, arguments, true);
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_2);
        il.Emit(OpCodes.Ldarg_3);
        il.Emit(OpCodes.Newobj, ci);
        il.Emit(OpCodes.Ret);
        return (Func<string, string, string, int, object>)dm.CreateDelegate(
            typeof(Func<string, string, string, int, object>));
    };

    public static object CreateInstance(string typeName, string where, string select, string order, int take)
    {
        var factory = factoryCache.GetOrAdd(typeName, CreateFactory);
        return factory(where, select, order, take);
    }
}

【讨论】:

  • 我会使用 Expression 树木...但只是因为它更容易编写代码...但Reflection.Emit 生成肯定更快。
  • @Terkhos 这是一个标准的 C# static 字段生命周期。在卸载 AppDomain 之前不应重新加载或清理。
  • @Terkhos 另请注意,您所做的性能测试并不相同。 Activator.CreateInstance 专门针对无参数构造函数进行了优化。这就是为什么如果你需要一个带参数的构造函数很重要。另一件事。上面的代码可能会受到长类型名字符串(计算哈希码)的影响。可以通过剥离命名空间并仅传递类名来改进它(命名空间将在实现GetType 调用中硬编码)。
  • @xanatos 绝对,好点!但会将改进留给 OP。
  • @Terkhos 这是关于将Dictionary 更改为ConcurentDictionary 并删除lock 语句。
猜你喜欢
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多