今天我在玩实体框架,我读到为 C# 生成的 IL 与以下代码的 VB.NET 不同:
VB.NET:
Dim ctx As New TravelEntities
Sub Main()
CallContext()
CallContext()
CallContext()
End Sub
Private Sub CallContext()
Dim someCustomer = From x In ctx.Customer
Where x.CustomerId.Equals(5)
Select x
Console.WriteLine(someCustomer.Count())
End Sub
C#:
private static TravelEntities ctx = new TravelEntities();
static void Main(string[] args)
{
CallContext();
CallContext();
CallContext();
}
private static void CallContext()
{
var someCustomer = from x in ctx.Customer
where x.CustomerId.Equals(5)
select x;
Console.WriteLine(someCustomer.Count());
}
它们产生以下 IL:
VB:
.method private static void CallContext() cil managed
{
// Code size 195 (0xc3)
.maxstack 7
.locals init ([0] class [System.Core]System.Linq.IQueryable`1<class VB_IL_Difference.Customer> someCustomer,
[1] class [System.Core]System.Linq.Expressions.ParameterExpression VB$t_ref$S0,
[2] class [System.Core]System.Linq.Expressions.Expression[] VB$t_array$S0,
[3] class [System.Core]System.Linq.Expressions.ParameterExpression[] VB$t_array$S1,
[4] class [System.Core]System.Linq.Expressions.ParameterExpression VB$t_ref$S1,
[5] class [System.Core]System.Linq.Expressions.ParameterExpression[] VB$t_array$S2)
IL_0000: nop
IL_0001: ldsfld class VB_IL_Difference.TravelEntities VB_IL_Difference.Module1::ctx
IL_0006: callvirt instance class [System.Data.Entity]System.Data.Objects.ObjectSet`1<class VB_IL_Difference.Customer> VB_IL_Difference.TravelEntities::get_Customer()
IL_000b: ldtoken VB_IL_Difference.Customer
IL_0010: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0015: ldstr "x"
IL_001a: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type,
string)
IL_001f: stloc.1
IL_0020: ldloc.1
IL_0021: ldtoken method instance int32 VB_IL_Difference.Customer::get_CustomerId()
IL_0026: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
IL_002b: castclass [mscorlib]System.Reflection.MethodInfo
IL_0030: call class [System.Core]System.Linq.Expressions.MemberExpression [System.Core]System.Linq.Expressions.Expression::Property(class [System.Core]System.Linq.Expressions.Expression,
class [mscorlib]System.Reflection.MethodInfo)
IL_0035: ldtoken method instance bool [mscorlib]System.Int32::Equals(int32)
IL_003a: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
IL_003f: castclass [mscorlib]System.Reflection.MethodInfo
IL_0044: ldc.i4.1
IL_0045: newarr [System.Core]System.Linq.Expressions.Expression
IL_004a: stloc.2
IL_004b: ldloc.2
IL_004c: ldc.i4.0
IL_004d: ldc.i4.5
IL_004e: box [mscorlib]System.Int32
IL_0053: ldtoken [mscorlib]System.Int32
IL_0058: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_005d: call class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object,
class [mscorlib]System.Type)
IL_0062: stelem.ref
IL_0063: nop
IL_0064: ldloc.2
IL_0065: call class [System.Core]System.Linq.Expressions.MethodCallExpression [System.Core]System.Linq.Expressions.Expression::Call(class [System.Core]System.Linq.Expressions.Expression,
class [mscorlib]System.Reflection.MethodInfo,
class [System.Core]System.Linq.Expressions.Expression[])
IL_006a: ldc.i4.1
IL_006b: newarr [System.Core]System.Linq.Expressions.ParameterExpression
IL_0070: stloc.3
IL_0071: ldloc.3
IL_0072: ldc.i4.0
IL_0073: ldloc.1
IL_0074: stelem.ref
IL_0075: nop
IL_0076: ldloc.3
IL_0077: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [mscorlib]System.Func`2<class VB_IL_Difference.Customer,bool>>(class [System.Core]System.Linq.Expressions.Expression,
class [System.Core]System.Linq.Expressions.ParameterExpression[])
IL_007c: call class [System.Core]System.Linq.IQueryable`1<!!0> [System.Core]System.Linq.Queryable::Where<class VB_IL_Difference.Customer>(class [System.Core]System.Linq.IQueryable`1<!!0>,
class [System.Core]System.Linq.Expressions.Expression`1<class [mscorlib]System.Func`2<!!0,bool>>)
IL_0081: ldtoken VB_IL_Difference.Customer
IL_0086: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_008b: ldstr "x"
IL_0090: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type,
string)
IL_0095: stloc.s VB$t_ref$S1
IL_0097: ldloc.s VB$t_ref$S1
IL_0099: ldc.i4.1
IL_009a: newarr [System.Core]System.Linq.Expressions.ParameterExpression
IL_009f: stloc.s VB$t_array$S2
IL_00a1: ldloc.s VB$t_array$S2
IL_00a3: ldc.i4.0
IL_00a4: ldloc.s VB$t_ref$S1
IL_00a6: stelem.ref
IL_00a7: nop
IL_00a8: ldloc.s VB$t_array$S2
IL_00aa: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [mscorlib]System.Func`2<class VB_IL_Difference.Customer,class VB_IL_Difference.Customer>>(class [System.Core]System.Linq.Expressions.Expression,
class [System.Core]System.Linq.Expressions.ParameterExpression[])
IL_00af: call class [System.Core]System.Linq.IQueryable`1<!!1> [System.Core]System.Linq.Queryable::Select<class VB_IL_Difference.Customer,class VB_IL_Difference.Customer>(class [System.Core]System.Linq.IQueryable`1<!!0>,
class [System.Core]System.Linq.Expressions.Expression`1<class [mscorlib]System.Func`2<!!0,!!1>>)
IL_00b4: stloc.0
IL_00b5: ldloc.0
IL_00b6: call int32 [System.Core]System.Linq.Queryable::Count<class VB_IL_Difference.Customer>(class [System.Core]System.Linq.IQueryable`1<!!0>)
IL_00bb: call void [mscorlib]System.Console::WriteLine(int32)
IL_00c0: nop
IL_00c1: nop
IL_00c2: ret
} // end of method Module1::CallContext
C#:
.method private hidebysig static void CallContext() cil managed
{
// Code size 141 (0x8d)
.maxstack 7
.locals init ([0] class [System.Core]System.Linq.IQueryable`1<class C_IL_Difference.Customer> someCustomer,
[1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,
[2] class [System.Core]System.Linq.Expressions.Expression[] CS$0$0001,
[3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002)
IL_0000: nop
IL_0001: ldsfld class C_IL_Difference.TravelEntities C_IL_Difference.Program::ctx
IL_0006: callvirt instance class [System.Data.Entity]System.Data.Objects.ObjectSet`1<class C_IL_Difference.Customer> C_IL_Difference.TravelEntities::get_Customer()
IL_000b: ldtoken C_IL_Difference.Customer
IL_0010: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0015: ldstr "x"
IL_001a: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type,
string)
IL_001f: stloc.1
IL_0020: ldloc.1
IL_0021: ldtoken method instance int32 C_IL_Difference.Customer::get_CustomerId()
IL_0026: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
IL_002b: castclass [mscorlib]System.Reflection.MethodInfo
IL_0030: call class [System.Core]System.Linq.Expressions.MemberExpression [System.Core]System.Linq.Expressions.Expression::Property(class [System.Core]System.Linq.Expressions.Expression,
class [mscorlib]System.Reflection.MethodInfo)
IL_0035: ldtoken method instance bool [mscorlib]System.Int32::Equals(int32)
IL_003a: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
IL_003f: castclass [mscorlib]System.Reflection.MethodInfo
IL_0044: ldc.i4.1
IL_0045: newarr [System.Core]System.Linq.Expressions.Expression
IL_004a: stloc.2
IL_004b: ldloc.2
IL_004c: ldc.i4.0
IL_004d: ldc.i4.5
IL_004e: box [mscorlib]System.Int32
IL_0053: ldtoken [mscorlib]System.Int32
IL_0058: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_005d: call class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object,
class [mscorlib]System.Type)
IL_0062: stelem.ref
IL_0063: ldloc.2
IL_0064: call class [System.Core]System.Linq.Expressions.MethodCallExpression [System.Core]System.Linq.Expressions.Expression::Call(class [System.Core]System.Linq.Expressions.Expression,
class [mscorlib]System.Reflection.MethodInfo,
class [System.Core]System.Linq.Expressions.Expression[])
IL_0069: ldc.i4.1
IL_006a: newarr [System.Core]System.Linq.Expressions.ParameterExpression
IL_006f: stloc.3
IL_0070: ldloc.3
IL_0071: ldc.i4.0
IL_0072: ldloc.1
IL_0073: stelem.ref
IL_0074: ldloc.3
IL_0075: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [mscorlib]System.Func`2<class C_IL_Difference.Customer,bool>>(class [System.Core]System.Linq.Expressions.Expression,
class [System.Core]System.Linq.Expressions.ParameterExpression[])
IL_007a: call class [System.Core]System.Linq.IQueryable`1<!!0> [System.Core]System.Linq.Queryable::Where<class C_IL_Difference.Customer>(class [System.Core]System.Linq.IQueryable`1<!!0>,
class [System.Core]System.Linq.Expressions.Expression`1<class [mscorlib]System.Func`2<!!0,bool>>)
IL_007f: stloc.0
IL_0080: ldloc.0
IL_0081: call int32 [System.Core]System.Linq.Queryable::Count<class C_IL_Difference.Customer>(class [System.Core]System.Linq.IQueryable`1<!!0>)
IL_0086: call void [mscorlib]System.Console::WriteLine(int32)
IL_008b: nop
IL_008c: ret
} // end of method Program::CallContext
看来此代码的 VB.NET 版本将在每次执行代码时联系数据库,而 C# 版本将在多次执行代码时从缓存中检索实体。
为什么他们会让两种语言表现得如此不同? 我的误解是,这两种语言只是语法不同,并且生成的 IL 几乎完全相同。
还有更多两种语言生成如此不同的 IL 的例子吗?
您看到的部分差异可能是由于调用了结尾的 Select x。由于 VB 查询语法中不需要它,但您显式声明了它,因此 VB 将其包含在编译中。您可以像下面一样轻松地表述 VB 语法:
Dim someCustomer = From x In ctx.Customer
Where x.CustomerId.Equals(5)
由于 C# 在编译时需要本质上无操作的 Select 子句,因此编译器会在生成的 IL 中对其进行优化。
我怀疑在此示例中,如果您使用 CustomerName =(=) "Foo",您会发现 VB 和 C# 生成的表达式树之间存在更大差异,因为 C# 和 VB 对字符串相等性的处理方式非常不同。我见过不少 LINQ 提供程序(包括 LINQ to Bing、LINQ to Twitter、EF Sample Query Provider、NOrm)无法在 VB 中解析 CustomerName = "Foo",因为他们只在 C# 中测试了表达式树解析。
至于您声称 C# 缓存结果,我没有看到针对 Northwind 使用以下代码(使用 LinqPad)。仍然在调用数据库3次。
void Main()
{
CallContext();
CallContext();
CallContext();
}
private void CallContext()
{
var someCustomer = from x in Customers
where x.CustomerID.Equals("ALFKI")
select x;
Console.WriteLine(someCustomer.Count());
}
每次调用 CallContext 方法时,这两个代码都会调用数据库。 差异仅在于为 LINQ 表达式生成的表达式树,在本例中差异不大。
正如您在评论中所说,现在您使用了 LINQ 方法链接而不是 LINQ 语法,这就是为什么现在生成的表达式树将是相同的。