快速创建对象而不是Activator.CreateInstance(类型)

本文关键字:CreateInstance 类型 Activator 创建对象 | 更新日期: 2023-09-27 18:01:04

我正在努力提高应用程序的性能。我们有很多Activator.CreateInstance的电话引起了一些悲伤。

我们实例化了许多基于接口(ITabDocument(的类,环顾四周后,我想到了使用以下代码:

该代码并不比使用我们的Activator.CreateInstance代码更好(事实上稍微慢一点(。

    public static Func<T> CreateInstance<T>(Type objType) where T : class, new()
    {
        var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
        ILGenerator ilGen = dynMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
        ilGen.Emit(OpCodes.Ret);
        return (Func<T>)dynMethod.CreateDelegate(typeof(Func<T>));
    }

我想知道为什么会这样,我所做的只是:

ITabDocument document = CreateInstance<ITabDocument>(Type.GetType("[Company].Something"));

有没有更好的方法来创建有助于上述功能的对象?当你不确定具体的类型时,这有点困难。

快速创建对象而不是Activator.CreateInstance(类型)

我在这两者之间做了一些基准测试(我会写下最基本的细节(:

public static T Instance() //~1800 ms
{
    return new T();
}
public static T Instance() //~1800 ms
{
    return new Activator.CreateInstance<T>();
}
public static readonly Func<T> Instance = () => new T(); //~1800 ms
public static readonly Func<T> Instance = () => 
                                 Activator.CreateInstance<T>(); //~1800 ms
//works for types with no default constructor as well
public static readonly Func<T> Instance = () => 
               (T)FormatterServices.GetUninitializedObject(typeof(T)); //~2000 ms

public static readonly Func<T> Instance = 
     Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();  
     //~50 ms for classes and ~100 ms for structs

正如CD所说,编译后的表达式是最快的,而且有很大的优势。除(T)FormatterServices.GetUninitializedObject(typeof(T))之外的所有方法仅适用于具有默认构造函数的类型

当每个泛型类型都有一个静态类时,缓存编译后的结果委托是微不足道的。类似:

public static class New<T> where T : new()
{
    public static readonly Func<T> Instance = Expression.Lambda<Func<T>>
                                              (
                                               Expression.New(typeof(T))
                                              ).Compile();
}

注意new约束。呼叫任何

MyType me = New<MyType>.Instance();

除了第一次将类加载到内存中之外,执行速度将是最快的。

为了拥有一个同时处理具有默认构造函数和不具有默认构造函数的类型的类,我采用了一种混合方法:

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();
    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();
        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();
        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}
public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

也将以有效的方式处理值类型。

请注意,(T)FormatterServices.GetUninitializedObject(t)对于string将失败。因此,对字符串进行了特殊处理以返回空字符串

这可能会有所帮助:不要使用Activator。CreateInstance或ConstructorInfo。调用,使用编译的lambda表达式:

// Make a NewExpression that calls the ctor with the args we just created
NewExpression newExp = Expression.New(ctor, argsExp);                  
// Create a lambda with the New expression as body and our param object[] as arg
LambdaExpression lambda = Expression.Lambda(typeof(ObjectActivator), newExp, param);            

// Compile it
ObjectActivator compiled = (ObjectActivator)lambda.Compile();

问题是,如果您要一次又一次地直接调用CreateInstance,而不是将结果保存在某个地方并一遍又一遍地使用该结果,那么您可能应该继续在其中进行缓存。

internal static class DelegateStore<T> {
     internal static IDictionary<string, Func<T>> Store = new ConcurrentDictionary<string,Func<T>>();
}
public static T CreateInstance<T>(Type objType) where T : class
{
    Func<T> returnFunc;
    if(!DelegateStore<T>.Store.TryGetValue(objType.FullName, out returnFunc)) {
        var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
        ILGenerator ilGen = dynMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
        ilGen.Emit(OpCodes.Ret);
        returnFunc = (Func<T>)dynMethod.CreateDelegate(typeof(Func<T>));
        DelegateStore<T>.Store[objType.FullName] = returnFunc;
    }
    return returnFunc();
}

更新日期:2022年10月13日
nawfal的回答以为基准

以NET6.0为基准,只是看到了这仍然是多么必要。

增加了Activator.CreateInstance<T>();测试和struct测试。

激活器1=new();
激活器2=Activator.CreateInstance<T>();
激活器3=New<T>.Instance();

TL;博士:仍然推荐用于简单的课程不要用于结构

using BenchmarkDotNet.Running;
using InstanceBenchmark;
//BenchmarkRunner.Run<ActivatorBenchmark<TestClass>>();
BenchmarkRunner.Run<ActivatorBenchmark<TestStruct>>();
public class TestClass
{
    public string Name { get; set; }
    public int Id { get; set; }
    public string Email { get; set; }
}
public struct TestStruct
{
    public string Name { get; set; }
    public int Id { get; set; }
    public string Email { get; set; }
}
[MemoryDiagnoser]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net60)]
[GenericTypeArguments(typeof(TestClass))]
[GenericTypeArguments(typeof(TestStruct))]
public class ActivatorBenchmark<T> where T : new()
{
    [Benchmark(Baseline = true)]
    [Arguments(1_000)]
    [Arguments(1_000_000)]
    [Arguments(100_000_000)]
    public void ActivatorTest1(int x)
    {
        for (int i = 0; i < x; i++)
        {
            var t = new T();
        }
    }
    [Benchmark]
    [Arguments(1_000)]
    [Arguments(1_000_000)]
    [Arguments(100_000_000)]
    public void ActivatorTest2(int x)
    {
        for (int i = 0; i < x; i++)
        {
            var t = Activator.CreateInstance<T>();
        }
    }
    [Benchmark]
    [Arguments(1_000)]
    [Arguments(1_000_000)]
    [Arguments(100_000_000)]
    public void ActivatorTest3(int x)
    {
        for (int i = 0; i < x; i++)
        {
            var t = New<T>.Instance();
        }
    }
}
public static class TestHelpers
{
    public static class New<T>
    {
        public static readonly Func<T> Instance = Creator();
        private static Func<T> Creator()
        {
            Type t = typeof(T);
            if (t == typeof(string))
            { return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile(); }
            if (t.HasDefaultConstructor())
            { return Expression.Lambda<Func<T>>(Expression.New(t)).Compile(); }
            return () => (T)FormatterServices.GetUninitializedObject(t);
        }
    }
    public static bool HasDefaultConstructor(this Type t)
    {
        return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
    }
}

分类结果

// * Summary *
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.1098/21H2)
Intel Core i9-10900KF CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK=6.0.402
  [Host]   : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
  .NET 6.0 : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
Job=.NET 6.0  Runtime=.NET 6.0  
|         Method |         x |           Mean |         Error |        StdDev | Ratio | RatioSD |        Gen0 |     Allocated | Alloc Ratio |
|--------------- |---------- |---------------:|--------------:|--------------:|------:|--------:|------------:|--------------:|------------:|
| ActivatorTest1 |      1000 |       9.946 μs |     0.1927 μs |     0.2142 μs |  1.00 |    0.00 |      3.8147 |      39.06 KB |        1.00 |
| ActivatorTest2 |      1000 |       9.808 μs |     0.0721 μs |     0.0674 μs |  0.98 |    0.02 |      3.8147 |      39.06 KB |        1.00 |
| ActivatorTest3 |      1000 |       6.219 μs |     0.1199 μs |     0.1427 μs |  0.63 |    0.02 |      3.8223 |      39.06 KB |        1.00 |
|                |           |                |               |               |       |         |             |               |             |
| ActivatorTest1 |   1000000 |   9,834.625 μs |    31.8609 μs |    26.6053 μs |  1.00 |    0.00 |   3812.5000 |   39063.26 KB |        1.00 |
| ActivatorTest2 |   1000000 |  10,671.712 μs |    47.0675 μs |    44.0269 μs |  1.09 |    0.01 |   3812.5000 |   39063.26 KB |        1.00 |
| ActivatorTest3 |   1000000 |   6,295.779 μs |   121.9964 μs |   186.3014 μs |  0.65 |    0.03 |   3820.3125 |    39062.5 KB |        1.00 |
|                |           |                |               |               |       |         |             |               |             |
| ActivatorTest1 | 100000000 | 995,902.729 μs | 7,355.4492 μs | 6,520.4141 μs |  1.00 |    0.00 | 382000.0000 | 3906325.27 KB |        1.00 |
| ActivatorTest2 | 100000000 | 982,209.783 μs | 6,630.1000 μs | 5,176.3460 μs |  0.99 |    0.01 | 382000.0000 | 3906335.95 KB |        1.00 |
| ActivatorTest3 | 100000000 | 618,402.807 μs | 4,305.6817 μs | 4,027.5373 μs |  0.62 |    0.01 | 382000.0000 | 3906253.48 KB |        1.00 |

结构结果

// * Summary *
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.1098/21H2)
Intel Core i9-10900KF CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK=6.0.402
  [Host]   : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
  .NET 6.0 : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
Job=.NET 6.0  Runtime=.NET 6.0
|         Method |         x |             Mean |         Error |        StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|--------------- |---------- |-----------------:|--------------:|--------------:|------:|--------:|----------:|------------:|
| ActivatorTest1 |      1000 |         212.8 ns |       4.27 ns |       4.38 ns |  1.00 |    0.00 |         - |          NA |
| ActivatorTest2 |      1000 |         209.5 ns |       0.10 ns |       0.09 ns |  0.98 |    0.02 |         - |          NA |
| ActivatorTest3 |      1000 |       1,646.0 ns |       2.69 ns |       2.10 ns |  7.77 |    0.14 |         - |          NA |
|                |           |                  |               |               |       |         |           |             |
| ActivatorTest1 |   1000000 |     204,577.8 ns |     128.30 ns |     107.14 ns |  1.00 |    0.00 |         - |          NA |
| ActivatorTest2 |   1000000 |     204,569.4 ns |     116.38 ns |     108.86 ns |  1.00 |    0.00 |         - |          NA |
| ActivatorTest3 |   1000000 |   1,644,446.5 ns |  12,606.12 ns |   9,842.03 ns |  8.04 |    0.05 |       1 B |          NA |
|                |           |                  |               |               |       |         |           |             |
| ActivatorTest1 | 100000000 |  20,455,141.5 ns |  12,934.68 ns |  12,099.11 ns |  1.00 |    0.00 |      15 B |        1.00 |
| ActivatorTest2 | 100000000 |  20,460,807.6 ns |  25,571.37 ns |  19,964.44 ns |  1.00 |    0.00 |      15 B |        1.00 |
| ActivatorTest3 | 100000000 | 164,105,645.0 ns | 327,107.27 ns | 305,976.34 ns |  8.02 |    0.01 |     898 B |       59.87 |

您可能会从生成相同的代码中获得一些开销。

ILGenerator为工厂动态创建代码。

创建某种类型的映射或您已经使用过的类型的Dictionary,并保留为该类型创建的工厂方法。

构造委托的泛型方法,直接调用构造函数。自动搜索具有给定委托类型签名的给定类型的构造函数,并构造该类型的委托。此处编码:

/// <summary>
/// Reflective object construction helper.
/// All methods are thread safe.
/// </summary>
public static class Constructor
{
    /// <summary>
    /// Searches an instanceType constructor with delegateType-matching signature and constructs delegate of delegateType creating new instance of instanceType.
    /// Instance is casted to delegateTypes's return type. 
    /// Delegate's return type must be assignable from instanceType.
    /// </summary>
    /// <param name="delegateType">Type of delegate, with constructor-corresponding signature to be constructed.</param>
    /// <param name="instanceType">Type of instance to be constructed.</param>
    /// <returns>Delegate of delegateType wich constructs instance of instanceType by calling corresponding instanceType constructor.</returns>
    public static Delegate Compile(Type delegateType,Type instanceType)
    {
        if (!typeof(Delegate).IsAssignableFrom(delegateType))
        {
            throw new ArgumentException(String.Format("{0} is not a Delegate type.",delegateType.FullName),"delegateType");
        }
        var invoke = delegateType.GetMethod("Invoke");
        var parameterTypes = invoke.GetParameters().Select(pi => pi.ParameterType).ToArray();
        var resultType = invoke.ReturnType;
        if(!resultType.IsAssignableFrom(instanceType))
        {
            throw new ArgumentException(String.Format("Delegate's return type ({0}) is not assignable from {1}.",resultType.FullName,instanceType.FullName));
        }
        var ctor = instanceType.GetConstructor(
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null);
        if(ctor == null)
        {
            throw new ArgumentException("Can't find constructor with delegate's signature","instanceType");
        }
        var parapeters = parameterTypes.Select(Expression.Parameter).ToArray();
        var newExpression = Expression.Lambda(delegateType,
            Expression.Convert(Expression.New(ctor, parapeters), resultType),
            parapeters);
        var @delegate = newExpression.Compile();
        return @delegate;
    }
    public static TDelegate Compile<TDelegate>(Type instanceType)
    {
        return (TDelegate) (object) Compile(typeof (TDelegate), instanceType);
    }
}

是雅皮士项目来源的一部分。使用它,您可以构造委托调用任何给定类型的构造函数,包括带参数的构造函数(ref和out参数除外(。

示例用法:

var newList = Constructor.Compile<Func<int, IList<String>>>(typeof (List<String>));
var list = newList(100);

在构造委托之后,将其存储在静态字典或具有泛型参数的类的静态字段中。不要每次都构造新的委托。使用一个委托来构造给定类型的多个实例。