C# 编译表达式以创建 T 的新实例并将值从 T 的另一个实例复制到其属性

C# Compiled expression to create new instance of T and copy values to its properties from another instance of T

我想知道是否可以创建一个仅在运行时已知的类型的实例,并使用编译表达式为该实例的属性赋值,如果可以,如何实现。

我有一个通用的 class,其方法接受 T 的一个实例和 returns 一个副本。 T 仅在运行时已知,或者更确切地说是 user/consumer 定义的。我知道如何用反射来做到这一点(假设它在这个例子中有一个空的构造函数并且没有异常处理或空检查以简化)。

public class MyClass<T>
{
    public T CreateCopy(T source)
    {
        var type = typeof(T);
        var copy = type.GetConstructor(Type.EmptyTypes).Invoke(null);
        foreach(var pi in type.GetProperties())
        {
            pi.SetValue(copy, pi.GetValue(source));
        }
        return copy;
    }
}

反射非常昂贵,经过一些挖掘我找到了一个选项,至少可以使用编译表达式创建 T 的实例。

var type = typeof(T);
Expression.Lambda<Func<T>>(Expression
   .New(type.GetConstructor(Type.EmptyTypes)
       ?? throw new InvalidOperationException(
           $"Type has to have an empty public constructor. {type.Name}")))
   .Compile();

经过一些基准测试后,我发现它的执行速度比 CreateCopy(...) 方法快大约 6 倍。问题是我不知道哪种类型将作为泛型传入以及它将具有多少属性。 有没有办法用编译后的表达式从 CreateCopy(...) 方法执行所有操作?

我已经查看了 Expression.AsignExpression.MemberInit,但我找不到合适的东西。 Expression.MemberInit 的问题在于它期望有一个 Expresssion.BindExpression.Constant 但我无法从传递的 T 实例中获取属性值。有办法吗?

谢谢。

P.S。所以我正在寻找类似的东西:

var type = typeof(T);
var propertyInfos = type.GetProperties();
var ctor = Expression.New(type.GetConstructor(Type.EmptyTypes));
var e = Expression.Lambda<Func<T, T>>(Expression
            .MemberInit(ctor, propertyInfos.Select(pi => 
                Expression.Bind(pi, Expression.Constant(pi.GetValue(source)))))).Compile();

你快到了。您需要定义一个参数,然后使用 属性 访问表达式分配属性,如下所示:

public static T Express<T>(T source)
{
   var parameter = Expression.Parameter(typeof(T), "source");
   var type = typeof(T);
   var ctor = Expression
              .New(type.GetConstructor(Type.EmptyTypes));
   var propertyInfos = type.GetProperties();
   var e = Expression
            .Lambda<Func<T, T>>(
              Expression
              .MemberInit(ctor, propertyInfos.Select(pi =>
                   Expression.Bind(pi, CanBeAssigned(pi.PropertyType)
                    ? (Expression)Expression.Property(parameter, pi.Name)
                    : Expression.Call(typeof(Program).GetMethod(nameof(Express))
                                   .MakeGenericMethod(pi.PropertyType),                                                                        
                                  Expression.Property(parameter, pi.Name)
                          ))
                 )),
                parameter
            );
            var x = e.Compile();
            var z = x(source);
            return z;
}

public static bool CanBeAssigned(Type t)
{
    return t.IsValueType || t.Name == "String"; // this might need some improvements
}