如何为类型为对象的 属性 setter 创建委托

How to create a delegate for a property setter with its type as object

我正在寻找一种在不知道 属性 类型的情况下为 属性 的 setter 创建委托的方法,而不是让 Action<TClass, TType> ,我要Action<TClass, object>。将调用 Action<TClass, object> 的代码总是将正确的基础类型装箱为 object。目的是稍后缓存这些委托。

请考虑以下示例。

public class MyClass<T>
{
    public T MyProperty { get; set; }
}

我可以设置MyProperty如下。

var myClass = new MyClass<int>();
var property = typeof(MyClass<int>).GetProperty("MyProperty");
object intAsObject = 2;

//Option 1. This works, but is slow
property.SetValue(myClass, intAsObject);

//Option 2. This works, but I need to know the type of MyProperty at compile time.
var setter = (Action<MyClass<int>, int>)property.SetMethod.CreateDelegate(typeof(Action<MyClass<int>, int>));
setter(myClass, 5);

//Option 3. This does not work. It throws ArgumentException. Is it possible to achieve something like this?!
var objSetter = (Action<MyClass<int>, object>)property.SetMethod.CreateDelegate(typeof(Action<MyClass<int>, object>));
objSetter(myClass, intAsObject);

如何实现选项#3?抛出 ArgumentException 的消息如下: 无法绑定到目标方法,因为它的签名或安全透明度与委托类型不兼容。

此外,是否可以让它适用于任何 T,即 intdoublestringobject 等?

我找到了有趣的答案,例如以下内容,但找不到实现我想要的方法。 Creating a property setter delegate

非常感谢您。

感谢 Iliar Turdushev 的评论,他指出了线程 here, and thanks also to this blog post here 我能够想出以下内容。

public static Delegate CreateSetter(PropertyInfo propertyInfo)
{
    ParameterExpression instance = Expression.Parameter(propertyInfo.ReflectedType, "instance");
    ParameterExpression propertyValue = Expression.Parameter(propertyInfo.PropertyType, "propertyValue");
    var body = Expression.Assign(Expression.Property(instance, propertyInfo.Name), propertyValue);
    return Expression.Lambda(body, instance, propertyValue).Compile();
}

然后就可以这样使用了(还是考虑我问题中的例子class)。

//The types are known for this example. But in my real scenario only an instance of PropertyInfo will be available.
var propertyInfo = typeof(MyClass<int>).GetProperty(nameof(MyClass<int>.MyProperty));

var deleg = CreateSetter(propertyInfo);
deleg.DynamicInvoke(myClass, 10);

//Prints 10 as expected
Console.WriteLine(myClass.MyProperty);

编辑: 刚发现下面的也可以:

public static Action<object, object> CreateSetter(PropertyInfo propertyInfo)
{
    ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
    UnaryExpression instanceExpresion = Expression.Convert(instance, propertyInfo.DeclaringType);

    ParameterExpression propertyValue = Expression.Parameter(typeof(object), "propertyValue");
    UnaryExpression propertyValueExpression = Expression.Convert(propertyValue, propertyInfo.PropertyType);

    var body = Expression.Assign(Expression.Property(instanceExpresion, propertyInfo.Name), propertyValueExpression);
    return Expression.Lambda<Action<object, object>>(body, instance, propertyValue).Compile();
}

并以这种方式使用。

var action = CreateSetter(propertyInfo);
action.Invoke(myClass, 10);