Close

Lambda Calculus et al in C# 3.0 / VS 2008 / .NET 3.5

Visual
Studio 2008 VHD
makes life a lot easier if you want to explore the upcfeature-set of  C# 3.0 compiler. Make sure to get
the differencing image Visual
Studio Code Name Orcas Base Image
else you’d be getting the following
error.

"OrcasBeta2_VSTS" could not be started because a disk-related error
occurred.

Feeling bad for fellow .netters (Yes Ben, its you) who had to clean install the beta2 upgrade from beta1. For me, it was plugging in the new machine 🙂 VM Rocks..errr...I meant virtual PC VM, not the other VM.

Anyhow, support for lambda expressions is one of the gems in C# 3.0,
as defined in the C#
3.0 Language Specification

lambda-expression:
(   lambda-parameter-listopt   )   =>   lambda-expression-body
implicitly-typed-lambda-parameter   =>   lambda-expression-body

This is essentially the same thing you’d do in CLisp, the
function definition, application and recursion, all in quite elegant way.
Therefore, the implication inherently defines the method without any explicit
declaration. So you can say

x => x + 1 which translates to a function that takes one argument, c, and
returns the value x + 1.

A less trivial example is as follows.

List<string>
XFilesEpisodes = new List<string>();
XFilesEpisodes.Add("Little
Green Men");
XFilesEpisodes.Add("The
Host");
XFilesEpisodes.Add("Blood");
XFilesEpisodes.Add("Sleepless");
XFilesEpisodes.Add("Duane
Barry");
string
episodeMatch = XFilesEpisodes.Find(p => p.Equals("Sleepless"));
Console.WriteLine(episodeMatch);
Console.Read();

p
=> p.Equals("Sleepless"));
expands to the

 string episodeMatch = XFilesEpisodes.Find(delegate(string name) {
                           
return XFilesEpisodes.Equals("Sleepless");
                        });

The
lambda methods evaulated within closures; these anonymous delegated blocks are not called closures because their understanding
would give closure to your intellectual feat but because they can access the
local members. As defined on precious
wikipedia
(since I can never find my PL book handy), a closure is a function that is
evaluated in an environment containing one or more bound variables. When called, the function can access
these variables.

Now on the IL level, this can be seen as a static predicate
class of anonymous delegate (hence the expansion)

.field
private static class [mscorlib]System.Predicate`1<string>
'<>9__CachedAnonymousMethodDelegate1'

.custom
instance void
[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() =
( 01 00 00 00 )

It is defined as anonymous delegate in the general metadata
and further performs a string comparison with virtual call (in bold below). 

.method
private hidebysig static bool  '<

Main>b__0'(string p) cil managed
{
  .custom instance void
[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() =
( 01 00 00 00 )
 
// Code size       12 (0xc)
  .maxstack  8
  IL_0000:  ldarg.0
 
IL_0001:  ldstr     
"Sleepless"

  IL_0006:  callvirt   instance bool
[mscorlib]System.String::Equals(string)

  IL_000b:  ret
} // end
of method Program::'<

Main>b__0'

The Main method is as follows which shows the generic list
of items and then the actual call to CachedAnonymousMethodDelegate.

 
.method
private hidebysig static void 

Main
(string[] args) cil managed
{
  .entrypoint
  // Code size       110 (0x6e)
  .maxstack  4
  .locals init ([0] class
[mscorlib]System.Collections.Generic.List`1<string> XFilesEpisodes,
           [1] string episodeMatch)
  IL_0000:  newobj     instance void class
[mscorlib]System.Collections.Generic.List`1<string>::.ctor()


.......

  IL_003e:  ldsfld     class [mscorlib]System.Predicate`1<string>
VS.NET2008Features.Program::'<>9__CachedAnonymousMethodDelegate1'
 
IL_0043:  brtrue.s   IL_0056
  IL_0045:  ldnull
 
IL_0046:  ldftn      bool VS.NET2008Features.Program::'<Main>b__0'(string)
  IL_004c:  newobj     instance void class
[mscorlib]System.Predicate`1<string>::.ctor(object,

                                                                                       native int)
 IL_0051:  stsfld     class
[mscorlib]System.Predicate`1<string> VS.NET2008Features.Program::'<>9__CachedAnonymousMethodDelegate1'

  IL_0056:  ldsfld     class
[mscorlib]System.Predicate`1<string>
VS.NET2008Features.Program::'<>9__CachedAnonymousMethodDelegate1'

  IL_005b:  callvirt   instance !0 class
[mscorlib]System.Collections.Generic.List`1<string>::Find(class
[mscorlib]System.Predicate`1<!0>)

  IL_0060:  stloc.1

  IL_0061:  ldloc.1

  IL_0062:  call       void
[mscorlib]System.Console::WriteLine(string)

  IL_0067:  call       int32
[mscorlib]System.Console::Read()

  IL_006c:  pop

  IL_006d:  ret

} // end
of method Program::Main

The extension methods are a very powerful feature. Think of
them as highly sophisticated version of poor man’s global overrides. For instance,
in the example below I’d convert all the temperature by “extending” the method
on the double type which does not define a Convert method.


namespace
VS.NET2008Features
{
   static class Program
   {
       static void Main(string[] args)
       {
           double temperature = 0;
           temperature.Convert();
           Console.Read();
       }

       static void Convert(this double temp)
       {
           double tempF = (9/5)* temp +32; //
9/5.0 not done on purpose.
           Console.WriteLine(tempF);
       }
   }

}

For a simple program like this, the underlying IL and
reflector code looks quite interesting. First of all, note the clever IL as it
will evaluate the 9/5 as 1 (integer value) since it is not a double calculation
(9/5.0 would have been). Also, the extension attribute is added and redirection
to this new extended method is done at the IL level. Clever eh?

 

private static void Main(string[] args)
{
    0.Convert();
    Console.Read();
}

private
static void Convert(this double temp)
{
    double num = (1 * temp) + 32;
    Console.WriteLine(num);
}

.method
private hidebysig static void  Convert(float64 temp) cil managed

{

  .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
= ( 01 00 00 00 )

  // Code size       29 (0x1d)

  .maxstack  2

  .locals init ([0] float64 tempF)

  IL_0000:  ldc.r8     1.

  IL_0009:  ldarg.0

  IL_000a:  mul

  IL_000b:  ldc.r8     32.

  IL_0014:  add

  IL_0015:  stloc.0

  IL_0016:  ldloc.0

  IL_0017:  call       void [mscorlib]System.Console::WriteLine(float64)

  IL_001c:  ret

} //
end of method Program::Convert

.method
private hidebysig static void 

Main
(string[] args) cil managed

{

  .entrypoint

  // Code size       23 (0x17)

  .maxstack  1

  .locals init ([0] float64 temperature)

  IL_0000:  ldc.r8     0.0

  IL_0009:  stloc.0

  IL_000a:  ldloc.0

  IL_000b:  call       void
VS.NET2008Features.Program::Convert(float64)

  IL_0010:  call       int32
[mscorlib]System.Console::Read()

  IL_0015:  pop

  IL_0016:  ret

} //
end of method Program::Main

The extension method is depicted here by setting the
extension attribute in the CLR. The call to the Convert gets translated and
redirected to VS.NET2008Features.Program::Convert(float64)

Scott Guthrie defines it best, from both developer and framework architect prospective. And till then, I'm waiting for Jeffrey Richter's addendum to CLR via C# which would discuss the C# 3.0 enhancements.

Share

3 thoughts on “Lambda Calculus et al in C# 3.0 / VS 2008 / .NET 3.5

Comments are closed.