Wednesday, October 29, 2008

Comprehension vs lambda queries



It's a very much debated argument like C# vs VB.NET lambda or comprehension queries (LINQ). Personally I prefer LINQ comprehension queries purely because they seem less functional and more imperative and to me they are easier to read.

C#, or VB if you prefer, support both.

Take the following simple LINQ query:
static void Main(string[] args)
{
string[] cities = { "London", "Paris", "New York" };
Console.WriteLine((from c in cities where c.Contains('L') select c).First());
}
Note we are still using extensions methods in the LINQ query. Now consider the lambda version:
static void Main(string[] args)
{
string[] cities = { "London", "Paris", "New York" };
Console.WriteLine(cities.Where(c => c.Contains("L")).First());
}
For me I prefer the LINQ version easier to read.

If we inspect the IL we will see identical generated code. First is the LINQ IL:
        
L_0023: ldsfld class [System.Core]System.Func`2 ConsoleApplication6.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_0028: brtrue.s L_003d
L_002a: ldnull
L_002b: ldftn bool ConsoleApplication6.Program::
b__0(string)
L_0031: newobj instance void [System.Core]System.Func`2::.ctor(object, native int)
L_0036: stsfld class [System.Core]System.Func`2 ConsoleApplication6.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_003b: br.s L_003d
L_003d: ldsfld class [System.Core]System.Func`2 ConsoleApplication6.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_0042: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Where(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [System.Core]System.Func`2)
L_0047: call !!0 [System.Core]System.Linq.Enumerable::First(class [mscorlib]System.Collections.Generic.IEnumerable`1)
L_004c: call void [mscorlib]System.Console::WriteLine(string)
L_0051: nop
L_0052: ret
.method private hidebysig static bool 
b__0(string c) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
.maxstack 2
.locals init (
[0] bool CS$1$0000)
L_0000: ldarg.0
L_0001: ldc.i4.s 0x4c
L_0003: call bool [System.Core]System.Linq.Enumerable::Contains(class [mscorlib]System.Collections.Generic.IEnumerable`1, !!0)
L_0008: stloc.0
L_0009: br.s L_000b
L_000b: ldloc.0
L_000c: ret
}
.field private static class [System.Core]System.Func`2 CS$<>9__CachedAnonymousMethodDelegate1
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
}
Now we have the lambda IL:
L_0023: ldsfld class [System.Core]System.Func`2 ConsoleApplication6.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_0028: brtrue.s L_003d
L_002a: ldnull
L_002b: ldftn bool ConsoleApplication6.Program::
b__0(string)
L_0031: newobj instance void [System.Core]System.Func`2::.ctor(object, native int)
L_0036: stsfld class [System.Core]System.Func`2 ConsoleApplication6.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_003b: br.s L_003d
L_003d: ldsfld class [System.Core]System.Func`2 ConsoleApplication6.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_0042: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Where(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [System.Core]System.Func`2)
L_0047: call !!0 [System.Core]System.Linq.Enumerable::First(class [mscorlib]System.Collections.Generic.IEnumerable`1)
L_004c: call void [mscorlib]System.Console::WriteLine(string)
L_0051: nop
L_0052: ret
method private hidebysig static bool 
b__0(string c) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
.maxstack 2
.locals init (
[0] bool CS$1$0000)
L_0000: ldarg.0
L_0001: ldstr "L"
L_0006: callvirt instance bool [mscorlib]System.String::Contains(string)
L_000b: stloc.0
L_000c: br.s L_000e
L_000e: ldloc.0
L_000f: ret
}
.field private static class [System.Core]System.Func`2 CS$<>9__CachedAnonymousMethodDelegate1
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
}
As you can see the code is almost identical. The only difference is in the calling of the Contains method. With LINQ we are calling Contains on IEnumerable whereas with lambda Contains is on the string class. You can see this in the IL b__0 method.

So it's really about personal choice.

No comments: