原始的问题是:“如何修改一个用reflection得到的field值,并且保证线程安全”。根据所采用的同步技术,解决方案各有不同。如果是采用lock,那么自然可以反射得到同步对象然后使用Monitor来同步。但如果对象采用的是Interlocked那一套方法,问题就变得有些麻烦了。其中最典型的,也就是如何Increment一个reflection得到的int型field。困难之处在于Increment方法需要ref int参数(编译时),而若使用FieldInfo的GetValue方法只能得到box后的值(运行时),对其进行修改已经没有意义。既然前者的需求无法在编译时满足,那就干脆把它的编译推迟。使用Expression Tree或者类似技术可以做到。下面展示如何自增目标类Foo中具有CounterAttribute的字段。
// This attribute indicates the field to be increased.
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class CounterAttribute : Attribute
{
}
public class Foo
{
// This counter will be increased.
[Counter]
public long Counter = 0;
}
class Program
{
static void Main(string[] args)
{
var foo = new Foo();
// Print the counter before increasement.
Console.WriteLine(foo.Counter);
// Do the increasement.
IncreaseCounter(foo);
// Print the increased counter.
Console.WriteLine(foo.Counter);
}
private static void IncreaseCounter(object instance)
{
// Get the target type whose public field is to be increased.
var targetType = instance.GetType();
// Get the target field.
var targetField = targetType.GetFields(BindingFlags.Public | BindingFlags.Instance)
.First(p => p.GetCustomAttributes(typeof (CounterAttribute), false).Any());
// The Interlocked.Increment method
var increaseMethod = typeof (Interlocked).GetMethod("Increment", new[] {typeof (long).MakeByRefType()});
// The parameter of the finally built lambda.
var parameter = Expression.Parameter(typeof (object));
// Cast the parameter into the target type.
var targetInstance = Expression.TypeAs(parameter, targetType);
// Access the target field of the target instance, and pass it as parameter to the call of Interlocked.Increment.
var call = Expression.Call(increaseMethod, Expression.Field(targetInstance, targetField));
// Build the lambda.
var lambda = Expression.Lambda(typeof (Action<object>), call, parameter);
// Compile into delegate.
var func = (Action<object>)lambda.Compile();
// Call the compiled delegate.
func(instance);
}
}
这样做必定会有一些性能损失,但是如果lambda的Compile与调用相比少得多的话,那么影响应该不至于十分显著;更何况,原本都已经使用了反射了。