开放委托是对没有目标的实例方法的委托。要调用它,您需要提供目标作为其第一个参数。它们是优化代码的巧妙方法,否则会使用反射并导致性能不佳。有关开放代表的介绍,请参阅this。在实践中使用它的方式是使用昂贵的反射代码来构建这些开放委托,但随后您将能够像简单的委托调用一样非常便宜地调用它们。
我正在尝试编写代码,将任意 PropertyInfo 转换为其设置器的委托。到目前为止我想出了这个:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace Test
{
class TestClass
{
static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
{
MethodInfo setMethod = property.GetSetMethod();
if (setMethod != null && setMethod.GetParameters().Length == 1) //skips over nasty index properties
{
//To be able to bind to the delegate we have to create a delegate
//type like: Action<T,actualType> rather than Action<T,object>.
//We use reflection to do that
Type setterGenericType = typeof(Action<,>);
Type delegateType = setterGenericType.MakeGenericType(new Type[] { typeof(T), property.PropertyType });
var untypedDelegate = Delegate.CreateDelegate(delegateType, setMethod);
//we wrap the Action<T,actualType> delegate into an Action<T,object>
Action<T, object> setter = (instance, value) =>
{
untypedDelegate.DynamicInvoke(new object[] { instance, value });
};
return setter;
}
else
{
return null;
}
}
int TestProp
{
set
{
System.Diagnostics.Debug.WriteLine("Called set_TestProp");
}
}
static void Test()
{
PropertyInfo property = typeof(TestClass).GetProperty("TestProp");
Action<TestClass, object> setter = MakeSetterDelegate<TestClass>(property);
TestClass instance = new TestClass();
setter(instance, 5);
}
}
}
将为 getter 编写类似的代码。它可以工作,但是 setter 委托使用 DynamicInvoke 从 Action
<derivedType
> 转换为 Action<object
>,我怀疑这占用了我所追求的优化的很大一部分。所以问题是:
DynamicInvoke
不会成为一名出色的二传手。在这里,针对通用内部类型的反射是更好的选择,因为这将允许您使用typed委托。另一种选择是 DynamicMethod
,但是您需要担心一些 IL 细节。
您可能想查看
HyperDescriptor
,它将IL工作包装到PropertyDescriptor
实现中。另一个选项是 Expression
API(如果您使用的是 .NET 3.5 或更高版本):
static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
{
MethodInfo setMethod = property.GetSetMethod();
if (setMethod != null && setMethod.GetParameters().Length == 1)
{
var target = Expression.Parameter(typeof(T));
var value = Expression.Parameter(typeof(object));
var body = Expression.Call(target, setMethod,
Expression.Convert(value, property.PropertyType));
return Expression.Lambda<Action<T, object>>(body, target, value)
.Compile();
}
else
{
return null;
}
}
或者使用泛型类型:
abstract class Setter<T>
{
public abstract void Set(T obj, object value);
}
class Setter<TTarget, TValue> : Setter<TTarget>
{
private readonly Action<TTarget, TValue> del;
public Setter(MethodInfo method)
{
del = (Action<TTarget, TValue>)
Delegate.CreateDelegate(typeof(Action<TTarget, TValue>), method);
}
public override void Set(TTarget obj, object value)
{
del(obj, (TValue)value);
}
}
static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
{
MethodInfo setMethod = property.GetSetMethod();
if (setMethod != null && setMethod.GetParameters().Length == 1)
{
Setter<T> untyped = (Setter<T>) Activator.CreateInstance(
typeof(Setter<,>).MakeGenericType(typeof(T),
property.PropertyType), setMethod);
return untyped.Set;
}
else
{
return null;
}
}
我曾经上过这门课。也许有帮助:
public class GetterSetter<EntityType,propType>
{
private readonly Func<EntityType, propType> getter;
private readonly Action<EntityType, propType> setter;
private readonly string propertyName;
private readonly Expression<Func<EntityType, propType>> propertyNameExpression;
public EntityType Entity { get; set; }
public GetterSetter(EntityType entity, Expression<Func<EntityType, propType>> property_NameExpression)
{
Entity = entity;
propertyName = GetPropertyName(property_NameExpression);
propertyNameExpression = property_NameExpression;
//Create Getter
getter = propertyNameExpression.Compile();
// Create Setter()
MethodInfo method = typeof (EntityType).GetProperty(propertyName).GetSetMethod();
setter = (Action<EntityType, propType>)
Delegate.CreateDelegate(typeof(Action<EntityType, propType>), method);
}
public propType Value
{
get
{
return getter(Entity);
}
set
{
setter(Entity, value);
}
}
protected string GetPropertyName(LambdaExpression _propertyNameExpression)
{
var lambda = _propertyNameExpression as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.Name;
}
测试:
var gs = new GetterSetter<OnOffElement,bool>(new OnOffElement(), item => item.IsOn);
gs.Value = true;
var result = gs.Value;
这是一个老问题,但有人可能仍然会发现以下答案有用。
我认为
DynamicInvoke
调用使得通过 MakeSetterDelegate
优化的整个想法毫无用处。结果几乎等同于仅使用 PropertyInfo.SetValue
(就性能而言)。
问题中的
MakeSetterDelegate
方法基于使用 Delegate.CreateDelegate
API,这是正确的方法。对原始想法进行一些更改将为我们提供所需的解决方案。添加表示 property
类型的通用参数,分离方法并使用 MethodInfo.MakeGenericMethod
反射 API。这是生成的代码(带有附加说明):
static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
{
// All the actual work is done in the MakeSetterDelegateCore<T, TValue> method below.
// We can't call it directly because of an unknown second generic type argument.
// So, we take advantage of the MethodInfo.MakeGenericMethod reflection API.
// First, we need to get the MethodInfo instance,
// which is actually a so-called "GenericMethodDefinition".
// Of course, we can use a typeof(TestClass).GetMethod(...) call to do this,
// but there is a more interesting, type-safe and reliable way – via delegate,
// which is demonstrated here:
var method = ((Func<PropertyInfo, Action<T, object>>)MakeSetterDelegateCore<T, object>)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(new[] { typeof(T), property.PropertyType });
// The performance cost of the MethodInfo.Invoke call is not an issue here:
// this is a one-time call compared to multiple uses of the resulting delegate.
return (Action<T, object>)method.Invoke(null, new object[] { property });
}
private static Action<T, object> MakeSetterDelegateCore<T, TValue>(PropertyInfo property)
{
MethodInfo setMethod = property.GetSetMethod();
if (setMethod != null && setMethod.GetParameters().Length == 1) //skips over nasty index properties
{
var typedSetter = (Action<T, TValue>)Delegate.CreateDelegate(typeof(Action<T, TValue>), setMethod);
Action<T, object> setter = (instance, value) =>
{
typedSetter(instance, (TValue)value);
};
return setter;
}
return null; // or perhaps it's better to throw some exception
}