【问题标题】:Increment value from expression从表达式增加值
【发布时间】:2014-09-04 18:03:20
【问题描述】:

我想写一个闭包并增加它的价值,但我做不到。这是我的代码

        int i = 0;
        Expression<Func<bool>> closurExpression = () =>
                                                  {
                                                      i++;
                                                      return i != 0;
                                                  };

但我收到关于A lambda expression with a statement body cannot be converted to an expression treeAn expression tree may not contain an assignment operator 等的多个错误。是否可以不使用诸如 Mono.Cecil 等外部工具?


对于问题:我为什么要问它。我想编写一个简单的包装器(用于计算调用次数的签名Func&lt;T,T&gt; at least。例如:

Wrapper<int> wrapper = () => 5;
for(int i = 0; i < 10; i++)
   wrapper();
int calls = wrapper.Calls; // 10;

我的第一个认识是:

class FunctionWithCounter<T, TResult>
{
    private readonly Func<T, TResult> function;
    public int Calls { get; set; }

    private FunctionWithCounter(Func<T, TResult> function)
    {
        Calls = 0;
        this.function = function;
    }
    public static implicit operator FunctionWithCounter<T, TResult>(Func<T, TResult> func)
    {
        return new FunctionWithCounter<T, TResult>(func);
    }

    public TResult this[T arg]
    {
        get
        {
            Calls++;
            return function(arg);
        }
    }
}

但在我得到递归函数后将无法正常工作。例如这段代码

int fact(int n) { return n < 2 ? 1 : n * fact(n - 1); }

任何 n 的调用计数都将为 1。所以想法是:获取源函数,并为每次调用方法增量注入调用增量。方法somefunc 的所有内部调用都应替换为我们的方法funcWithInjection,在这种情况下,我们也将捕获递归函数。这是我的代码,但它不起作用(除了注入,所以这段代码确实增加了一个字段值,但我不能将源的方法体添加到尾部并编译它,你可以玩它):

public class FunctionWithCounter<T, TResult> where T : new()
{
    private readonly Func<T, TResult> _functionWithInjection;
    private int _calls;
    public int Calls
    {
        get
        {
            return _calls;
        }
    }

    public FunctionWithCounter(Func<T, TResult> function)
    {
        _calls = 0;
        var targetObject = function.Target ?? new object();
        var dynMethod = new DynamicMethod(new Guid().ToString(), typeof(TResult), new[] { targetObject.GetType(), typeof(T), typeof(FunctionWithCounter<T, TResult>) }, true);

        var ilGenerator = GenerateInjection(dynMethod);
        ilGenerator.Emit(OpCodes.Ret);

        var resDelegate = dynMethod.CreateDelegate(typeof(Func<T, FunctionWithCounter<T, TResult>, TResult>), targetObject);
        var functionWithInjection = (Func<T, FunctionWithCounter<T, TResult>, TResult>)resDelegate;
        var targetMethodBody = function.Method.GetMethodBody();
        Debug.Assert(targetMethodBody != null, "mbody != null");

        //here i need to write method body in the tail of dynamic method.

        _functionWithInjection = function;
        _functionWithInjection = t =>
        {
            functionWithInjection(t, this);
            return default(TResult);
        };
        //finally here should be _functionWithInjection = t => functionWithInjection(t, this);
    }

    private ILGenerator GenerateInjection(DynamicMethod method)
    {
        var callsFieldInfo = GetType().GetField("_calls", BindingFlags.NonPublic | BindingFlags.Instance);
        Debug.Assert(callsFieldInfo != null, "callsFieldInfo != null");
        var ilGenerator = method.GetILGenerator();
        ilGenerator.Emit(OpCodes.Nop);
        ilGenerator.Emit(OpCodes.Ldarg_2);
        ilGenerator.Emit(OpCodes.Dup);
        ilGenerator.Emit(OpCodes.Ldfld, callsFieldInfo);
        ilGenerator.Emit(OpCodes.Ldc_I4_1);
        ilGenerator.Emit(OpCodes.Add);
        ilGenerator.Emit(OpCodes.Stfld, callsFieldInfo);
        return ilGenerator;
    }

    public static implicit operator FunctionWithCounter<T, TResult>(Func<T, TResult> func)
    {
        return new FunctionWithCounter<T, TResult>(func);
    }

    public TResult this[T arg]
    {
        get
        {
            return _functionWithInjection(arg);
        }
    }
}

我的第二个实现是基于Emit API,但是太复杂了,搞了半天没完成……

所以现在这是我的第三次尝试,我想使用表达式。 Шt 应该是这样的:

    public FunctionWithCounter(Expression<Func<T, TResult>> function)
    {
        Action action = () => _calls++;
        Expression<Action> closurExpression = () => action();
        var result = Expression.Block(closurExpression, function);
        _functionWithInjection = Expression.Lambda<Func<T,TResult>>(result).Compile();
    }

对不起我的英语,但我真的很想实现这个想法

【问题讨论】:

  • 你真正想要达到什么目的?我已经给出了答案,但这实际上是一个绕过编译器的确切规则同时仍然违反这些规则的精​​神的情况。

标签: c# .net reflection code-generation linq-expressions


【解决方案1】:

好吧,你可以使用Interlocked.Increment来回避规则:

int i = 0;
Expression<Func<bool>> expression = () => Interlocked.Increment(ref i) != 0;

...但我会非常谨慎地这样做。考虑到所涉及的副作用,我不希望有很多处理表达式树的代码能够非常干净地处理这个问题。

就我所期望的行为而言,上述方法似乎工作

int i = -2;
Expression<Func<bool>> expression = () => Interlocked.Increment(ref i) != 0;
var del = expression.Compile();
Console.WriteLine(del()); // True
Console.WriteLine(del()); // False
Console.WriteLine(del()); // True
Console.WriteLine(del()); // True
Console.WriteLine(i);     // 2

【讨论】:

    【解决方案2】:

    我认为您的第一种方法更简洁,适用于简单的非递归情况。 如果你允许你的包装函数意识到递归,你可以更进一步,让包装器成为函数本身的参数:

     class RecursiveFunctionWithCounter<T, TResult> 
     {
        private readonly Func<T, RecursiveFunctionWithCounter<T, TResult>, TResult> function;
        public int Calls { get; set; }
    
        public RecursiveFunctionWithCounter(Func<T, RecursiveFunctionWithCounter<T, TResult>, TResult> function)
        {
            Calls = 0;
            this.function = function;
        }
    
    
        public TResult this[T arg]
        {
            get
            {
                Calls++;
                return function(arg, this);
            }
        }
     }
    

    并像这样使用它:

    var rw = new RecursiveFunctionWithCounter<int, int>(
            (n, self) => { return n < 2 ? 1 : n * self[n - 1]; }
    );
    
    int rr = rw[3]; // rr=6
    int rc = rw.Calls; // rc=3
    

    另一方面,如果您真正想做的是在代码中检测一些现有方法,请考虑做一点Aspect Oriented Programming(例如,使用PostSharp,这是一个递增方面的example每个方法调用的性能计数器)。这样你就可以在你的方法中添加一个像IncrementPerformanceCounterAttribute这样的属性,剩下的就交给AOT库了。

    【讨论】:

    • 至少,它有效(不像我的解决方案)。如果不考虑性能,那就太好了。 AOP 是什么:我不想对外部代码使用任何属性,一切都应该在这个类内部完成。对于用户来说,它不应该与简单的委托不同,除了附加字段调用和foo[x] 概念而不是foo(x)。也许,Rosylin 后处理可以帮助我,但它还没有完成
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-11
    • 2012-04-06
    • 1970-01-01
    • 2020-11-30
    • 1970-01-01
    相关资源
    最近更新 更多