Expression<TDelegate> 子类的属性在 C# 中是固有的吗?

Are properties of Expression<TDelegate> subclasses intrinsic in C#?

我想知道 Expression class 及其子 class 的属性(UnaryExpressionMemberExpression 等)是否已折叠在编译期间内联常量对象?

例如,在 ASP.Net MVC 中,通常的做法是强类型 属性 视图模型的名称,例如 Html.LabelFor(vm => vm.FirstName)),而 Html.LabelFor 需要一个表达式,为此然后检查 Body.Member.Name 等。这些属性是在运行时评估的(使用运行时编译成本等)还是在编译期间优化(如 .Net 4.5 中的 [CallerMemberName])?

您可以使用任何反编译器或 linqpad 查看反编译后的代码。它将创建一系列 Expression.XXX 调用以在运行时构建表达式。

例如下面的C#代码:

Expression<Func<string>> foo = () => "";

编译成:

IL_0000:  nop         
IL_0001:  ldstr       ""
IL_0006:  ldtoken     System.String
IL_000B:  call        System.Type.GetTypeFromHandle
IL_0010:  call        System.Linq.Expressions.Expression.Constant
IL_0015:  ldc.i4.0    
IL_0016:  newarr      System.Linq.Expressions.ParameterExpression
IL_001B:  call        System.Linq.Expressions.Expression.Lambda
IL_0020:  stloc.0     // foo
IL_0021:  ret    

它们被重新创建...即使在最简单的情况下,Expressions 总是被重新创建:

static Expression<Func<int, int>> otherFunc;

public static void MyMethod(Expression<Func<int, int>> func)
{
    if (otherFunc == null)
    {
        otherFunc = func;
        Console.WriteLine("First time");
    }
    else
    {
        Console.WriteLine("Same Expression<Func<int>>: {0}", object.ReferenceEquals(func, otherFunc));
        Console.WriteLine("Same Parameter: {0}", object.ReferenceEquals(func.Parameters[0], otherFunc.Parameters[0]));
    }
}

for (int i = 0; i < 2; i++)
{
    MyMethod(x => 5);
}

结果:

First time
Same Expression<Func<int>>: False
Same Parameter: False

这里我们在一个方法中,我们在 for 中调用 MyMethod,但 Expression 每次都不同。

"evaluated" 是什么意思?

未编译。所有必要的信息已经在编译时准备好了。所以从这个意义上说,它不是 "evaluated" 多次。表达式没有运行时编译,这不是表达式树的意义所在(除非您显式调用Compile,这基本上会将表达式树转换为方法委托)。

然而,表达式树每次创建:

for (int i = 0; i < 1000000; i++)
{
  Expression<Func<DataTable, string>> e = t => t.TableName;
}

在循环的每一步中,都会一次又一次地创建表达式,即使它被证明是恒定的(所有 Expression 都是不可变的,并且没有闭包)。

这不是 免费,但也不是很昂贵 - 此示例代码每秒产生大约 17 万个表达式(这是一个非常愚蠢的基准测试,但并非如此不利于大概估计)。

部分成本是必须创建的所有 Expression 个实例。有些是涉及的反思。更简单的表达式 更容易创建。

但是,您还需要考虑 左右的成本:

  • 所以在 UI 的上下文中?成本被收益广泛抵消。
  • I/O?在任何 I/O 操作旁边都可以忽略不计。
  • 深入了解数百万个将两个数字相加的元素的循环?不要 :)

如果您发现它在某处(配置文件、配置文件、配置文件)造成瓶颈,您通常可以缓存表达式树——正如我所说,它们是不可变的,所以这样做既安全又容易。事实上,您可以轻松地创建具有在运行时替换参数的巨大表达式树 - 由于表达式的不变性,您只需要创建您实际需要替换的表达式 - 其余的将保持不变。

在 ASP.NET MVC 的情况下,您使用这样的方法可以轻松访问对象元数据。您在这里获得的巨大好处是编译时检查您引用的属性和字段 - 它使您的应用程序保持一致变得容易得多,同时,代码非常容易阅读("It's a label for the FirstName property, duh.") 写。它会通过重构与您保持同步。