【问题标题】:Compiling a lambda expression results in delegate with Closure argument编译 lambda 表达式会产生带有闭包参数的委托
【发布时间】:2011-12-17 15:07:48
【问题描述】:

当我使用Expression.Lambda( ... ).Compile() 从表达式树创建委托时,结果是第一个参数为Closure 的委托。

public static Func<T, T, T> CreateTest<T>()
{
    ParameterExpression a = Expression.Parameter( typeof( T ) );
    ParameterExpression b = Expression.Parameter( typeof( T ) );
    Expression addition = Expression.Add( a, b );

    return (Func<T, T, T>)Expression.Lambda( addition, a, b ).Compile();
}

...

// 'addition' equals
// Int32 lambda_method(
//     System.Runtime.CompilerServices.Closure,
//     Int32,
//     Int32 )
Func<int, int, int> addition = DelegateHelper.CreateTest<int>();
int result = addition( 5, 5 );

我可以通过普通代码轻松调用委托,无需传递Closure 对象,但是这个Closure 来自哪里?

如何动态调用这个委托?

// The following does not work.
// Exception: MethodInfo must be a runtime MethodInfo object.    
MethodInfo additionMethod = addition.Method;
int result = (int)additionMethod.Invoke( null, new object[] { 5, 5 } );

使用表达式树看起来我必须传递 Closure 对象。

PropertyInfo methodProperty
    = typeof( Delegate ).GetProperty( "Method", typeof( MethodInfo ) );
MemberExpression getDelegateMethod
    = Expression.Property( Expression.Constant( addition ), methodProperty );
Func<MethodInfo> getMethodInfo
    = (Func<MethodInfo>)Expression.Lambda( getDelegateMethod ).Compile();
// Incorrect number of arguments supplied for call to method
// 'Int32 lambda_method(System.Runtime.CompilerServices.Closure, Int32, Int32)'
Expression call
    = Expression.Call(
        getMethodInfo(),
        Expression.Constant( 5 ), Expression.Constant( 5 ) );

这是一个简单的例子,它本身没有意义。我实际上想要实现的是能够包装例如Func&lt;Action&lt;SomeObject&gt;&gt;Func&lt;Action&lt;object&gt;&gt;。我已经可以为非嵌套代表做到这一点。这在反射期间很有用,as discussed here

我应该如何正确初始化这个Closure 对象,或者如何防止它出现?

【问题讨论】:

  • @JonSkeet:我会尽力而为,问题是整个示例非常复杂。我正在尝试递归调用以前编译的委托。当我尝试提取问题的一小部分时,here 你已经可以找到整个函数。
  • 是的,缩短它肯定会有所帮助:)
  • @JonSkeet:作为更多信息,目标是允许创建包装函数,例如Action&lt;Action&lt;SomeType&gt;&gt; 可以被 Action&lt;Action&lt;object&gt;&gt; 包裹。这在 my previous implementation 上继续,只能对 Action&lt;SomeType&gt;Action&lt;object&gt; 执行此操作。
  • 你意识到这不是类型安全的,对吧? Action&lt;T&gt; 是逆变的,但 Action&lt;Action&lt;T&gt;&gt; 实际上是协变的。
  • 是的,但我在反射期间使用它,我知道类型对应,但我不想知道具体类型,所以我可以支持任何类型。我在博客上写了in detail here

标签: c# lambda expression-trees


【解决方案1】:

您看到的Closure 类型是一个实现细节。 MSDN 对此非常明确:

此 API 支持 .NET Framework 基础结构,但不支持 旨在直接从您的代码中使用。表示运行时 动态生成的方法的状态。

表达式树可以有状态。

闭包实例将包含 lambda 表达式关闭的所有非文字常量。它还可以包含用于表达式树中嵌套 lambda 的委托链。

为此,表达式树编译器使用了一个可爱的小技巧。它使用DynamicMethod 在内存代码中生成,即定义为静态的。然而,他们正在创建一个代表“closed over its first argument”。这意味着 CLR 会将委托的目标字段作为静态方法的第一个参数传递,因此您不必这样做。有效地向您隐藏 Closure 参数。

您的问题的解决方案很简单,不要尝试调用方法,调用委托,或者在使用反射时使用Delegate.DynamicInvoke,或者在表达式树的上下文中使用Expression.Invoke

【讨论】:

  • 这太好了,谢谢!我的函数通过使用新的MethodInfo 调用自身来递归地工作。我创建了一个接受委托并使用Expression.Invoke 调用它的新委托。经过一些重大重构后,我将提交更改。我现在可以成功包装例如Func&lt;SomeType, Func&lt;int&gt;&gt;Func&lt;SomeType, Func&lt;EnumType&gt;&gt;。具有强类型枚举的通用计算。 :) 爱它。 IEnumerable&lt;FlagsEnum&gt; setFlags = EnumHelper&lt;FlagsEnum&gt;.GetFlaggedValues( flags );
  • K,这在 .NET 4.0 中使用 Enum 也是可能的,但我可以在许多其他场景中使用这个“委托包装器”。我与Enum 做了一个简短的效率比较,差异可以忽略不计。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-01
  • 1970-01-01
  • 2015-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-30
相关资源
最近更新 更多