怎样Interlocked.Increment一个反射得到的field?

原始的问题是:“如何修改一个用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与调用相比少得多的话,那么影响应该不至于十分显著;更何况,原本都已经使用了反射了。

posted on 2012-01-29 22:40  Gildor Wang  阅读(531)  评论(0编辑  收藏  举报

导航