C#中的动态构造函数 [英] Dynamic constructor in C#

查看:96
本文介绍了C#中的动态构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写方法 GetDynamicConstructor< T> ,该方法本质上将返回给定类的智能构造函数。它将接受字符串数组作为参数并将其解析为适当的类型(给定现有的构造函数数据)。

I am trying to write a method GetDynamicConstructor<T> which will return, essentially, a smart constructor for the given class. It will accept an array of strings as parameters and parse them as the appropriate type (given existing constructor data).

public void Init()
{
    DynamicConstructor<MyClass> ctor = GetDynamicConstructor<MyClass>();
    MyClass instance = ctor(new string[] { "123", "abc" }); // parse "123" as int
}

public delegate T DynamicConstructor<T>(string[] args);

public DynamicConstructor<T> GetDynamicConstructor<T>()
{
    ConstructorInfo originalCtor = typeof(T).GetConstructors().First();

    ParameterInfo[] paramsInfo = originalCtor.GetParameters();

    for (int i = 0; i < paramsInfo.Length; i++) {
        Type paramType = paramsInfo[i].ParameterType;

        // This is as far as I got :D
    }

    return null;
}

public class MyClass
{
    int n;
    string s;

    public MyClass(int n, string s)
    {
        this.n = n;
        this.s = s;
    }
}

我基本上想要的是从 MyClass 这样的方法。

What I want, basically, is to construct from MyClass a method which looks like this.

public MyClass Example(string[] args)
{
    return new MyClass(int.Parse(args[0]), args[1]);
}

这里只有基本类型,因此我可以指望<$ c

There will only be basic types here so I can count on a Parse existing for the types I might run into.

如何编写 GetDynamicConstructor<的正文? T>

推荐答案

根据您要使用的精确程度,有几种方法它。 Steve16351有一种方法是为方法创建委托,该委托在执行时进行所有反射。另一种方法是在执行时生成类似于您的Example方法的委托,然后将其缓存。不同之处在于前者可以更灵活,而后者则可以更快。在每次执行时使用反射可以在选择构造函数之前考虑哪些转换成功。尽管已编译的委托人必须在参数可用之前知道要选择哪个构造函数,但它的性能特征更像是用C#本机编写的方法。以下是使用表达式树生成委托的实现。您希望针对每种类型缓存此文件,以实现最佳性能:

Depending on how exactly you want use this there are a couple ways to do it. Steve16351 has one way which is to create a delegate to a method that does all the reflection at execution time. Another way would be to generate a delegate which looks like your Example method at execution time, which is then cached. The difference would be that the former can be more flexible, while the latter would be faster. Using reflection on each execution can take into account which conversions are successful before selecting the constructor. While a compiled delegate has to know which constructor to select before arguments are available, it will have performance characteristics more like a method that had been written natively in C#. Below is in an implementation to generate the delegate using expression trees. You'd want to cache this for each type for maximum performance:

using System.Linq.Expressions;

public static DynamicConstructor<T> GetDynamicConstructor<T>()
{
    ConstructorInfo originalCtor = typeof(T).GetConstructors().First();

    var parameter = Expression.Parameter(typeof(string[]), "args");
    var parameterExpressions = new List<Expression>();

    ParameterInfo[] paramsInfo = originalCtor.GetParameters();
    for (int i = 0; i < paramsInfo.Length; i++)
    {
        Type paramType = paramsInfo[i].ParameterType;

        Expression paramValue = Expression.ArrayIndex(parameter, Expression.Constant(i));
        if (paramType.IsEnum)
        {
            var enumParse = typeof(Enum).GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(Type), typeof(string) }, null);
            var call = Expression.Call(null, enumParse, new[] { Expression.Constant(paramType), paramValue });
            paramValue = Expression.Convert(call, paramType);
        }
        else if (paramType != typeof(string))
        {
            var parseMethod = paramType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
            if (parseMethod == null)
            {
                throw new Exception($"Cannot find Parse method for type {paramType} (parameter index:{i})");
            }

            paramValue = Expression.Call(null, parseMethod, new[] { paramValue });
        }            

        parameterExpressions.Add(paramValue);
    }

    var newExp = Expression.New(originalCtor, parameterExpressions);
    var lambda = Expression.Lambda<DynamicConstructor<T>>(newExp, parameter);
    return lambda.Compile();
}

请注意,我添加了枚举的处理,因为不能将Parse称为相同

Note that I added handling of enums since Parse can't be called the same way as other simple types.

更新:

根据评论,这里是一个扩展版本,发出非-将处理默认参数值的通用委托:

Based on comments here is an expanded version to emit a non-generic delegate that will handle default parameter values:

    public static DynamicConstructor GetDynamicConstructor(Type type)
    {
        ConstructorInfo originalCtor = type.GetConstructors().First();

        var parameter = Expression.Parameter(typeof(string[]), "args");
        var parameterExpressions = new List<Expression>();

        ParameterInfo[] paramsInfo = originalCtor.GetParameters();
        for (int i = 0; i < paramsInfo.Length; i++)
        {
            Type paramType = paramsInfo[i].ParameterType;

            // added check for default value on the parameter info.
            Expression defaultValueExp;
            if (paramsInfo[i].HasDefaultValue)
            {
                defaultValueExp = Expression.Constant(paramsInfo[i].DefaultValue);
            }
            else
            {
                // if there is no default value, then just provide 
                // the type's default value, but we could potentially 
                // do something else here
                defaultValueExp = Expression.Default(paramType);
            }

            Expression paramValue;

            paramValue = Expression.ArrayIndex(parameter, Expression.Constant(i));
            if (paramType.IsEnum)
            {
                var enumParse = typeof(Enum).GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(Type), typeof(string) }, null);
                var call = Expression.Call(null, enumParse, new[] { Expression.Constant(paramType), paramValue });
                paramValue = Expression.Convert(call, paramType);
            }
            else if (paramType != typeof(string))
            {
                var parseMethod = paramType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
                if (parseMethod == null)
                {
                    throw new Exception($"Cannot find Parse method for type {paramType} (parameter index:{i})");
                }

                paramValue = Expression.Call(null, parseMethod, new[] { paramValue });
            }

            // here we bounds check the array and emit a conditional expression
            // that will provide a default value if necessary. Equivalent to 
            // something like i < args.Length ? int.Parse(args[i]) : default(int);  
            // Of course if the parameter has a default value that is used instead, 
            // and if the target type is different (long, boolean, etc) then
            // we use a different parse method.
            Expression boundsCheck = Expression.LessThan(Expression.Constant(i), Expression.ArrayLength(parameter));
            paramValue = Expression.Condition(boundsCheck, paramValue, defaultValueExp);

            parameterExpressions.Add(paramValue);
        }

        var newExp = Expression.New(originalCtor, parameterExpressions);
        var lambda = Expression.Lambda<DynamicConstructor>(newExp, parameter);
        return lambda.Compile();
    }
}

这篇关于C#中的动态构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆