簡體   English   中英

C#表達式可動態實例化類型而無需反射

[英]C# Expression to dynamically instantiate type without reflection

我試圖創建一種對象工廠來實例化實現特定接口的類型,其中在運行時之前不知道要實例化的類型,並且我試圖在不使用反射的情況下實現這一點。 我發現一些示例可能會暗示使用Expressions,但是我沒有設法找到適合我的情況的示例。 可能是根本不可能,但我想在這里提出這個問題以確保。

所以我到目前為止是這樣的:

public static Func<Type, object[], IMyInterface> FactoryExpression =
    Expression.Lambda<Func<Type, object[], IMyInterface>>(
        /* Something that creates an instance of the type with given arguments */
    ).Compile()

public static IMyInterface GetTypeOfMyInterface()
{
    Type t = Type.GetType(GetTypeNameFromSomewhere());
    ConstructorInfo c = t.GetConstructors().First();
    object[] args = ResolveCostructorArguments(c.GetParameters());

    return FactoryExpression(t, args);
}

我對這些類型的表達式沒有任何經驗。 是否有可能讓它正常工作,還是我不得不退一步思考?

編輯:

通過使用喬恩·漢娜的示例,我得出以下結論:

public class TypeInitializer<TResult>
{
    private static readonly ConcurrentDictionary<string, Func<object[], TResult>> InstanceCreationMethods =
        new ConcurrentDictionary<string, Func<object[], TResult>>();

    public static TResult CreateInstance(ConstructorInfo constructorInfo, params object[] arguments)
    {
        ParameterInfo[] parameterInfo = constructorInfo.GetParameters();
        IEnumerable<Type> parameterTypes = parameterInfo.Select(p => p.ParameterType);
        string constructorSignatureKey = GetConstructorSignatureKey(constructorInfo.DeclaringType, parameterTypes);

        Func<object[], TResult> factoryMethod = InstanceCreationMethods.GetOrAdd(constructorSignatureKey, key =>
                                                                                                         {
                                                                                                             Expression[] args = new Expression[parameterInfo.Length];
                                                                                                             ParameterExpression param = Expression.Parameter(typeof(object[]));
                                                                                                             for (int i = 0; i < parameterInfo.Length; i++)
                                                                                                                 args[i] = Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), parameterInfo[i].ParameterType);
                                                                                                             return Expression
                                                                                                                 .Lambda<Func<object[], TResult>>(Expression.Convert(Expression.New(constructorInfo, args), typeof(TResult)), param)
                                                                                                                 .Compile();
                                                                                                         });

        return factoryMethod(arguments);
    }

    private static string GetConstructorSignatureKey(Type type, IEnumerable<Type> argumentTypes) => string.Concat(type.FullName, " (", string.Join(", ", argumentTypes.Select(at => at.FullName)), ")");
}

哪個似乎可以按預期工作! 為此非常感謝。 為了試驗起見,我還使用Activator.CreateInstance和ConstructorInfo.Invoke進行了實現,並構建了som性能測試以了解兩者之間的區別。

00:00:00.9246614, Actiator
00:00:00.7524483, Constructor Invoke
00:00:00.8235814, Compiled Expression

該測試使用每種方法定時創建了100 000個相同類型的實例,並打印了結果。 令我驚訝的是,構造函數調用方法的接縫性能更好!

您可以使用Expression.New創建實例。

您還需要將ConstructorInfo傳遞給工廠,並使用Expression.Convert將對象轉換為接口。

public static Func<object[], IMyInterface> BuildFactoryExpression(ConstructorInfo ctor)
{
    ParameterInfo[] par = ctor.GetParameters(); // Get the parameters of the constructor
    Expression[] args = new Expression[par.Length];
    ParameterExpression param = Expression.Parameter(typeof(object[])); // The object[] paramter to the Func
    for (int i = 0; i != par.Length; ++i)
    {
        // get the item from the array in the parameter and cast it to the correct type for the constructor
        args[i] = Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), par[i].ParameterType);
    }
    return Expression.Lambda<Func<object[], IMyInterface>>(
        // call the constructor and cast to IMyInterface.
        Expression.Convert(
            Expression.New(ctor, args)
        , typeof(IMyInterface)
        ), param
    ).Compile();
}

是的,我也已經通過反射解決了此類問題,這非常簡單。 但是在這種情況下,我希望盡可能避免性能下降。

這仍在使用反射。 如果要重復使用相同的Func並且Compile()編譯為IL(例如不是UWP)的上下文中運行,那么您可能會從中受益,因為反射一次用於創建可重復使用的委托,從那時起就像將委托作為C#方法編寫一樣。 如果您可以鍵入參數而不是傳遞object[]並從中強制轉換,那么您將獲得更多。

如果您一次使用這些委托,那么最好只使用反射。 如果僅在解釋表達式的委托的上下文中,則無論如何都必須在內部使用反射。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM