使用表达式访问C#中的结构属性
本文关键字:结构 属性 表达式 访问 | 更新日期: 2023-09-27 18:28:32
我一直在使用以下代码缓存属性getter/setter委托,以便快速访问该功能:
class PropertyHelper
{
public static Func<object, object> BuildGetter(PropertyInfo propertyInfo)
{
var method = propertyInfo.GetGetMethod(true);
var obj = Expression.Parameter(typeof(object), "o");
Expression<Func<object, object>> expr =
Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method),
typeof(object)),
obj);
return expr.Compile();
}
public static Action<object, object> BuildSetter(PropertyInfo propertyInfo)
{
var method = propertyInfo.GetSetMethod(true);
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
Action<object, object> action = expr.Compile();
return action;
}
}
当访问类对象的属性时,这非常有效,但当我将其用于结构对象时,它会失败。例如,考虑以下代码:
public struct LocationStruct
{
public double X { get; set; }
public double Y { get; set; }
}
public class LocationClass
{
public double X { get; set; }
public double Y { get; set; }
}
public class Tester
{
public static void TestSetX()
{
Type locationClassType = typeof(LocationClass);
PropertyInfo xProperty = locationClassType.GetProperty("X");
Action<object, object> setter = PropertyHelper.BuildSetter(xProperty);
LocationStruct testLocationClass = new LocationClass();
setter(testLocationClass, 10.0);
if (testLocationClass.X == 10.0)
{
MessageBox.Show("Worked for the class!");
}
Type locationStructType = typeof(LocationStruct);
xProperty = locationStructType.GetProperty("X");
setter = PropertyHelper.BuildSetter(xProperty);
LocationStruct testLocationStruct = new LocationStruct();
setter(testLocationStruct, 10.0);
if (testLocationStruct.X != 10.0)
{
MessageBox.Show("Didn't work for the struct!");
}
}
}
第一部分工作,将testLocationClass的X值设置为10。但是,因为LocationStruct是一个结构,所以testLocationStruct是通过值传入的,该值(委托调用的方法内部)的X设置为10,但上面代码块中的testLocationStruct对象保持不变。
因此,我需要一种访问结构对象属性的方法,类似于上面的方法(它只适用于类对象的属性)。我曾尝试使用"引用传递"模式来实现这一点,但我就是无法实现。
任何人都能提供类似的BuildGetter和BuildSetter方法,用于缓存结构属性值的getter/setter委托吗?
要使其工作,您需要注意两件事:
- 创建setter表达式树时,需要将
Expression.Unbox
用于值类型,将Expression.Convert
用于引用类型 - 使用值类型调用setter时,需要确保它已装箱,以便使用指向结构的指针来设置值(而不是处理结构的副本)
新的实现看起来是这样的(只显示了新的setter和测试方法,因为其余的都是一样的):
public static Action<object, object> BuildSetter(PropertyInfo propertyInfo)
{
// Note that we are testing whether this is a value type
bool isValueType = propertyInfo.DeclaringType.IsValueType;
var method = propertyInfo.GetSetMethod(true);
var obj = Expression.Parameter(typeof (object), "o");
var value = Expression.Parameter(typeof (object));
// Note that we are using Expression.Unbox for value types
// and Expression.Convert for reference types
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
isValueType ?
Expression.Unbox(obj, method.DeclaringType) :
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj, value);
Action<object, object> action = expr.Compile();
return action;
}
以及调用编译后的setter的代码:
...
Type locationStructType = typeof (LocationStruct);
xProperty = locationStructType.GetProperty("X");
setter = PropertyHelper.BuildSetter(xProperty);
LocationStruct testLocationStruct = new LocationStruct();
// Note the boxing of the struct before calling the setter
object boxedStruct = testLocationStruct;
setter(boxedStruct, 10.0);
testLocationStruct = (LocationStruct)boxedStruct;
...
此打印:
Worked for the class!
Worked for the struct!
我还准备了一个.Net fiddle,展示了这里的工作实现:https://dotnetfiddle.net/E6WZmK
有关Expression.Unbox
步骤的说明,请参见此答案:https://stackoverflow.com/a/32158735/521773
结构作为参数是通过值传递的,而ref/out似乎不能很好地与表达式一起使用,您可以考虑使用返回结构实例的新函数签名:
static Func<MethodInfo, object, object, object> s1 = (MethodInfo set, object instance, object val) =>
{
set.Invoke(instance, new object[] { val });
return instance;
};
// Non-Generic approach
static Func<object, object, object> BuildSetter5(PropertyInfo propertyInfo)
{
var method = propertyInfo.GetSetMethod(true);
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Func<object, object, object>> expr =
Expression.Lambda<Func<object, object, object>>(
Expression.Call(
s1.Method,
Expression.Constant(method),
obj,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
Func<object, object, object> action = expr.Compile();
return action;
}
// Generic approach
static Func<T, object, T> BuildSetter6<T>(PropertyInfo propertyInfo) where T : struct
{
var method = propertyInfo.GetSetMethod(true);
var obj = Expression.Parameter(typeof(T), "o");
var value = Expression.Parameter(typeof(object));
Expression<Func<T, object, T>> expr =
Expression.Lambda<Func<T, object, T>>(
Expression.Convert(
Expression.Call(
s1.Method,
Expression.Constant(method),
Expression.Convert(obj, typeof(object)),
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
typeof(T)),
obj,
value);
Func<T, object, T> action = expr.Compile();
return action;
}