Expression<TDelegate> 子类的属性在 C# 中是固有的吗?
Are properties of Expression<TDelegate> subclasses intrinsic in C#?
我想知道 Expression
class 及其子 class 的属性(UnaryExpression
、MemberExpression
等)是否已折叠在编译期间内联常量对象?
例如,在 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
它们被重新创建...即使在最简单的情况下,Expression
s 总是被重新创建:
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.") 和写。它会通过重构与您保持同步。
我想知道 Expression
class 及其子 class 的属性(UnaryExpression
、MemberExpression
等)是否已折叠在编译期间内联常量对象?
例如,在 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
它们被重新创建...即使在最简单的情况下,Expression
s 总是被重新创建:
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.") 和写。它会通过重构与您保持同步。