What is Lambda
geek, howto, linq, programming, tech April 20th, 2008Something from a previous post was a very small line of code that looked like it was a code fragment used as an argument to a function call.
cruncher.Expect(x => x.Add(2, 4)).Returns(6); cruncher.Expect(yyy => yyy.Add(5, 2)).Returns(3);
Which brings us to a fork in the road. Down one path you can say, “Well that’s interesting. Some magic was added to dotnet somewhere along the line I might use here or there,” and down the other path you can say, “Well that’s interesting. What is this doing, and exactly how does it work under the hood, because I’ll probably use this here or there and I want to make sure I’m using the the right tool for the job and I’m using it in the right way.”
Going down either path is fine, but this post is for people who want to go down the second one.
Whenever I explain something I like to start with something to connect it with. In this case I came up with a song my mother-in-law used to listen to by Lyle Lovett - If I had a boat.
If I had a boat
I’d go out on the ocean
And if I had a pony
I’d ride him on my boat
And we could all together
Go out on the ocean
I said me upon my pony on my boat
So let’s see some code to put that song in context:
class Program
{
static public void Example()
{
Location theOcean = new Location { Name = "the ocean" };
Action<Boat> x1 = boat => boat.GoOut(theOcean);
Boat myBoat = new Boat { Name = "my boat" };
x1(myBoat); // my boat go out on the ocean
}
}
class Boat
{
public string Name { get; set; }
public void GoOut(Location location)
{
Console.WriteLine("{0} go out on {1}", this.Name, location.Name);
}
}
class Location
{
public string Name { get; set; }
}
The line that’s the doing the magic here is where the local variable Action<Boat> x1 is assigned with the value boat => boat.GoOut(theOcean). If you ever see a type of Action or Func you can relax - they’re nothing more than delegates with template parameters for the arguments.
In other words the Action variable x1 holds reference to a function that takes a boat as a parameter. The difference between Func and Action is that the last template parameter of Func is the return value, while the return value of Action is always void.
The value that’s being assigned into it is boat => boat.GoOut(theOcean). That’s where the song lyrics relate. Even though this looks like a function call, the method GoOut is not called at this time, and in fact the myBoat variable and Boat instance are not even in existance yet. What this is saying is “If I had a Boat (in a boat variable as far as I’m concerned) then I would use that boat to GoOut on theOcean (using theOcean Location variable in this scope).”
“Okay,” you’re now saying, “that’s interesting. It’s a slightly shorter way of declaring an inline delegate function. I’ve saved myself some keystrokes. Yay.”
But wait! There’s more! Let’s look at what Reflector is showing us has taken place.
public static void Example()
{
Location <>g__initLocal0 = new Location();
<>g__initLocal0.Name = "the ocean";
Location theOcean = <>g__initLocal0;
Action<Boat> x1 = delegate (Boat boat) {
boat.GoOut(theOcean);
};
Boat <>g__initLocal1 = new Boat();
<>g__initLocal1.Name = "my boat";
Boat myBoat = <>g__initLocal1;
x1(myBoat);
}
D’oh! The people behind the compiler are one step ahead of us. It is producing the same IL you would have if you had simply used an inline delegate - so that’s what Reflector has deduced the code for this method would have been.
As an aside there’s also an example of what compiler is producing when you’re using the property assignment syntax after a constructor - it’s about what you’d expect - it’s creating the object normally in a generated variable, setting the properties, and then using the object as it was intended. In this case they’re simply assigned to a local variable.
But that’s not going to stop us! Because we know that before the expression was used to make a delegate it was an Expression. So let’s use it as such.
class Program
{
static public void Example()
{
Location theOcean = new Location { Name = "the ocean" };
Expression<Action<Boat>> e1 = boat => boat.GoOut(theOcean);
Action<Boat> x1 = e1.Compile();
Boat myBoat = new Boat { Name = "my boat" };
x1(myBoat); // my boat go out on the ocean
theOcean = new Location { Name = "the pacific ocean" };
x1(myBoat); // my boat go out on the pacific ocean
}
}
And when we look at Reflector we can see how the compiler has given us what we’re looking for. An Expression is really an object. Actually it’s a fair number of related objects which model the statement. When the compiler hit this line it produced the IL needed to allocate and connect the expression objects that model this statement. Here’s approximately the csharp code Reflector believes would produce the same IL:
static public void Example()
{
Location theOcean = new Location { Name = "the ocean" };
ParameterExpression CS_0_0000;
Expression<Action<Boat>> e1 = Expression.Lambda<Action<Boat>>(
Expression.Call(
CS_0_0000 = Expression.Parameter(typeof(Boat), "boat"),
typeof(Boat).GetMethod("GoOut"),
new Expression[] { Expression.Constant(theOcean) }
),
new ParameterExpression[] { CS_0_0000 });
Action<Boat> x1 = e1.Compile();
Boat myBoat = new Boat { Name = "my boat" };
x1(myBoat); // my boat go out on the ocean
theOcean.Name = "the pacific ocean";
x1(myBoat); // my boat go out on the pacific ocean
theOcean = new Location { Name = "the atlantic ocean" };
x1(myBoat); // my boat go out on the pacific ocean
}
You can compile an Expression into a delegate and execute the code it refers to, as we see above, but the real power comes from the fact that you can take a step back and work with the expression itself as a piece of information.
“And if I had a pony I’d ride him on my boat.”
See how Lyle’s not saying because I have a pony I’m riding it on my boat now. It’s more hypothetical. Expression<Action<IPony, ILocation>> LyleSays = (pony, boat) => pony.RideOn(boat); so he’s saying “hypothetically, given a pony and a location I’ll refer to as a boat for the sake of argument, I’d ride one on the other.” He’s even put the statement out there as a line of a song you can parse and make of what you will.
That last part is where the spooky-cool magic powers start to come from. Let’s take a look at the MoQ example again.
var cruncher = new Mock<ICruncher>();
cruncher.Expect(x => x.Add(2, 4)).Returns(6);
What we have done is called a Mock<Cruncher> method named Expect that takes an Expression<Action<ICruncher>> parameter. The parameter we provided says if I had a cruncher I called x I’d call Add on it with a 2 and a 4. The Mock takes this expression we’ve provided, looks into it’s guts, sees it’s a method call with these parameters, and saves that information internally in a collection of rules.
Later when the totally fake cruncher gets a call on the Add method it whips through the rules looking for matching parameters and performs whatever action was added to that rule.
So in this case the expression is never even compiled and certainly never executed directly. It’s treated as data. The nice thing about this data though is that it’s been through the intellisense, and the compiler, so you know it’s good to go. It will also be updated if you use Visual Studio or Resharper to rename the Add method to something like Sum. Or even if the rename is missed it’ll be a compiler error because there’s no Add method.
This is also a corner-stone behind some of the new technologies that convert fairly complex expressions into sql statements (Linq to SQL, Linq to NHibernate) or that are used to calculate what url would be used for a given method call (Microsoft MVC).
Source code with this article is available online for browsing. Or to get a local copy use:
svn co http://dev.dejardin.org/svn/example/trunk/WhatIsLambda
(Though honestly it’s a pretty darn trivial and contrived example)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
namespace IfIHadABoat.Part4
{
interface ILocation
{
string Name { get; set; }
}
interface IPony : ILocation
{
void Ride(ILocation location);
}
interface IBoat : ILocation
{
void GoOut(ILocation location);
}
class Program
{
static public void Example()
{
Action<IBoat, ILocation> x1 = (boat, place) => boat.GoOut(place);
Action<IPony, ILocation> x2 = (pony, place) => pony.Ride(place);
Action<IBoat, IPony, ILocation> x3 = (boat, pony, place) =>
{
if (boat != null) x1(boat, place);
if (pony != null) x2(pony, boat ?? place);
};
var myPony = new Pony { Name = "my pony" };
var myBoat = new Boat { Name = "my boat" };
var theOcean = new Location { Name = "the ocean" };
x3(myBoat, myPony, theOcean);
// my boat go out on the ocean
// my pony ride on my boat
x3(null, new Pony(), new Location {Name = "through the desert"});
//no name ride on through the desert
}
}
class Location : ILocation
{
public string Name { get; set; }
}
class Pony : IPony
{
public string Name { get; set; }
public void Ride(ILocation location)
{
Console.WriteLine("{0} ride on {1}", this.Name ?? "no name", location.Name);
}
}
class Boat : IBoat
{
public string Name { get; set; }
public void GoOut(ILocation location)
{
Console.WriteLine("{0} go out on {1}", this.Name ?? "no name", location.Name);
}
}
}
Updated: I added a hackish loop in the final version of program.cs that turns an object graph into an xml infoset, and then puts that infoset onto the console. Here is the infoset for the following lambda expression:
Expression<Action<IBoat, IPony, ILocation>> e3 = (boat, pony, place) =>
pony.Ride(boat == null ? place : boat.GoOut(place));
<!--(boat, pony, place) => pony.Ride(IIF((boat = null), place, Convert(boat.GoOut(place))))-->
<e NodeType="Lambda">
<!--pony.Ride(IIF((boat = null), place, Convert(boat.GoOut(place))))-->
<MethodCallExpression-Body NodeType="Call">
<RuntimeMethodInfo-Method Name="Ride" />
<!--pony-->
<ParameterExpression-Object Name="pony" NodeType="Parameter">
<RuntimeType-Type Name="IPony" />
</ParameterExpression-Object>
<Arguments>
<!--IIF((boat = null), place, Convert(boat.GoOut(place)))-->
<ConditionalExpression NodeType="Conditional">
<!--(boat = null)-->
<BinaryExpression-Test IsLifted="False" IsLiftedToNull="False" NodeType="Equal">
<!--boat-->
<ParameterExpression-Left Name="boat" NodeType="Parameter">
<RuntimeType-Type Name="IBoat" />
</ParameterExpression-Left>
<!--null-->
<ConstantExpression-Right NodeType="Constant">
<RuntimeType-Type Name="Object" />
</ConstantExpression-Right>
<RuntimeType-Type Name="Boolean" />
</BinaryExpression-Test>
<!--place-->
<ParameterExpression-IfTrue Name="place" NodeType="Parameter">
<RuntimeType-Type Name="ILocation" />
</ParameterExpression-IfTrue>
<!--Convert(boat.GoOut(place))-->
<UnaryExpression-IfFalse IsLifted="False" IsLiftedToNull="False" NodeType="Convert">
<!--boat.GoOut(place)-->
<MethodCallExpression-Operand NodeType="Call">
<RuntimeMethodInfo-Method Name="GoOut" />
<!--boat-->
<ParameterExpression-Object Name="boat" NodeType="Parameter">
<RuntimeType-Type Name="IBoat" />
</ParameterExpression-Object>
<Arguments>
<!--place-->
<ParameterExpression Name="place" NodeType="Parameter">
<RuntimeType-Type Name="ILocation" />
</ParameterExpression>
</Arguments>
<RuntimeType-Type Name="IBoat" />
</MethodCallExpression-Operand>
<RuntimeType-Type Name="ILocation" />
</UnaryExpression-IfFalse>
<RuntimeType-Type Name="ILocation" />
</ConditionalExpression>
</Arguments>
<RuntimeType-Type Name="IPony" />
</MethodCallExpression-Body>
<Parameters>
<!--boat-->
<ParameterExpression Name="boat" NodeType="Parameter">
<RuntimeType-Type Name="IBoat" />
</ParameterExpression>
<!--pony-->
<ParameterExpression Name="pony" NodeType="Parameter">
<RuntimeType-Type Name="IPony" />
</ParameterExpression>
<!--place-->
<ParameterExpression Name="place" NodeType="Parameter">
<RuntimeType-Type Name="ILocation" />
</ParameterExpression>
</Parameters>
<RuntimeType-Type Name="Action`3" />
</e>
Recent Comments