Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Designing With Lambdas - Part IV

My previous posts in this series revolved around using lambdas to encapsulate logic. In this post I'll show a different side of the lambdas, the expressions.

Lambda Expressions

We have already seen that lambdas can be used to create anonymous methods to be passed wherever a delegate is expected. That's not the end of the story, though.

Lambdas can also be used where an Expression<DelegateType> is expected. The difference is that the compiler will not resolve the lambda to an anonymous method. Instead the lambda will be converted to an expression tree, which is a way to describe the code in the lambda.

If you used LINQ to SQL for example, you have already been using lambdas that are converted to expressions. Expressions are what enable LINQ to SQL to convert a LINQ query like this:

from cust in Customers
  where cust.City == "London" 
  && cust.ContactTitle == "Sales Representative"
  select cust
into a SQL query like this:
SELECT 
	[t0].[CustomerID], [t0].[CompanyName], 
	[t0].[ContactName], [t0].[ContactTitle], 
	[t0].[Address], [t0].[City], [t0].[Region], 
	[t0].[PostalCode], [t0].[Country], 
	[t0].[Phone], [t0].[Fax]
FROM [Customers] AS [t0]
WHERE 
	([t0].[City] = "London") 
	AND ([t0].[ContactTitle] = "Sales Representative")

In particular, pay attention to how the where clause was converted from C# syntax to SQL syntax.

Yet another JSON converter

Let's illustrate how expressions can be used by creating some code to convert objects to JSON.

What I will try to produce here is a converter that let's me specify which properties of my object will be included in the JSON version of the object.

One way to accomplish this could be by simply passing a list of property names to a conversion function that would work some reflection magic and extract the properties into the JSON format.

string userJson = ConvertToJson(userObj, "Name", "Email", "Age");

The biggest problem here, in my opinion, is not the reflection activity behind the scene, it's those strings. If you change the name of any of the properties the compiler and the refactoring tools won't be able to help you updating those strings. You may end up with runtime errors.

With lambdas and expressions we can do better. Here's the syntax that I will build.

class Company               
{                   
	public string Name { get; set; }      
	public int CompanyID { get; set; }      
	public string Address { get; set; }     
	public bool PlatinumCustomer { get; set; }    
	public string InternalControlNumber { get; set; } 
}          

// object that we will convert
var acme = new Company {        
	Name = "ACME, Inc." ,       
	InternalControlNumber = "3X77-AC",  
	Address = "123 Main St, Anytown, ST", 
	PlatinumCustomer = true,      
	CompanyID = 789         
};                

//convert it but only copy a few properties
string json = ConvertToJson<Company>(acme,  
	c => c.Name,        
	c => c.CompanyID,     
	c => c.PlatinumCustomer);

The resulting JSON string, stored in the json variable will be:

{"Name":"ACME, Inc.", "CompanyID":789, "PlatinumCustomer":true}

Each of the lambdas passed in contain just one access to an existing property. As soon as you start typing this you'll already see the advantage of working with strongly-typed constructs like this. IntelliSense will automatically list the Company properties and methods once you type c.. Also, if you rename one of the properties, the IDE will offer to rename all references to that property, and the lambda will also be included in the renaming. Lastly, your code won't even compile if you make a typo.

The code for ConvertToJson will receive an array of expressions of type Expression<Func<T, object>>. This type looks complicated but if we think about it from the inside out, Func<T, object> is something you typically see in regular lambdas. It's a delegate type. As soon as we decorate that delegate type with an Expression< T > around it, the compiler will continue to accept the lambda syntax for anonymous methods but now the method never gets invoked, it will become an expression tree, which is then passed to the function. Here's the function.

public static string ConvertToJson<T>(                 
	T data,                            
	params Expression<Func<T, object>>[] properties)         
{                                
	int exportedFields = 0;                      
	StringBuilder json = new StringBuilder();                
	json.Append("{");                        
	foreach(var prop in properties)                    
	{                                                  
		string propName = null;                    
		object propVal = null;                   
                                 
		if(prop.Body.NodeType == ExpressionType.MemberAccess)          
		{                          
			PropertyInfo pi = 
				(PropertyInfo)((MemberExpression)prop.Body).Member;  
			propVal = pi.GetValue(data, null);             
			propName = pi.Name;                  
		}                          
		else if(prop.Body.NodeType == ExpressionType.Convert)          
		{                          
			UnaryExpression expr = (UnaryExpression)prop.Body;       
			PropertyInfo pi = 
				(PropertyInfo)((MemberExpression)expr.Operand).Member;
			propVal = pi.GetValue(data, null);             
			propName = pi.Name;                  
		}                          
		                           
		if(propName != null)                     
		{                          
			string stringVal = null;                 
			if(propVal == null) stringVal = "null";            
			else if(propVal is string) stringVal = "\"" + propVal + "\"";    
			else if(propVal is bool ||               
				propVal is byte ||               
				propVal is int ||                
				propVal is long ||               
				propVal is short)                
					stringVal = propVal.ToString().ToLower();  
                                 
			if(exportedFields > 0) json.Append(", ");         
			json.AppendFormat(@"""{0}"":{1}", propName, stringVal);      
			exportedFields++;                  
		}                          
	}                              
                                 
	json.Append("}");                        
	return json.ToString();                      
}

The above code is just for illustration purposes. It does not handle (for example) properties that are other complex objects. A more complete version can be written but it could get very long and beyond the scope of this particular example.

In the code we can see that the function receives the object to be converted and also an array of expressions. Each expression describes a function that accepts a parameter of type T (Company in our sample) and returns an object. Expressions ca get very complex but we are simplifying our life by only accepting one-liners that call one of the object's property.

With each expressions we inspect them to see if we can find the part that mentions the property information, we get its name, and then we get the property value. After some trivial formatting of the value, we add the name/value pair to the JSON result.

How to choose Expressions over Delegates

The key is the execution or not of the lambda. If the lambda will eventually be invoked, which I'd argue will be the majority of the cases, then you need a simple delegate. When what you need is a way to represent a structure or data item that has an appropriate version in code (like our properties or the table columns in the LINQ to SQL example,) and maybe later you need to transform the code version in some other form, or use it somewhat like configuration, then you're probably looking at an opportunity to employ expression trees.


Posted 05-08-2008 11:41 PM by sergiopereira

[Advertisement]

Comments

Reflective Perspective - Chris Alcock » The Morning Brew #90 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #90
on 05-09-2008 3:03 AM

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #90

Dew Drop - May 9, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - May 9, 2008 | Alvin Ashcraft's Morning Dew
on 05-09-2008 8:48 AM

Pingback from  Dew Drop - May 9, 2008 | Alvin Ashcraft's Morning Dew

Christopher Steen wrote Link Listing - May 8, 2008
on 05-09-2008 8:56 AM

Link Listing - May 8, 2008

Christopher Steen wrote Link Listing - May 8, 2008
on 05-09-2008 8:56 AM

Sharepoint Creating Hierarchical Menus with a CustomAction in SharePoint [Via: Jan Tielens ] WPF Metadata...

Bryan Watts wrote re: Designing With Lambdas - Part IV
on 05-09-2008 12:53 PM

Nice application of expression trees. I couldn't help but Linqify the meat of the method:

var pairs =

 from property in properties

 let memberEx = (...ternary...)

 where memberEx.Member is PropertyInfo

 let propertyInfo = (PropertyInfo) memberEx.Member

 select new

 {

   Name = propertyInfo.Name,

   Value = GetJsonValue(propertyInfo.GetValue(data, null))

 };

foreach(var pair in pairs)

{

 ...Build JSON...

}

Links Today (2008-05-10) wrote Links Today (2008-05-10)
on 05-10-2008 12:42 PM

Pingback from  Links Today (2008-05-10)

James Curran wrote re: Designing With Lambdas - Part IV
on 05-12-2008 11:33 AM

>> # string json = ConvertToJson<Company>(acme,    

#     c => c.Name,          

#     c => c.CompanyID,      

#     c => c.PlatinumCustomer);

<<

Unfortunately, I don't have immediate access to a lambda-enabled compiler to test this, but as the first parameter is strongly-typed, the type parameter should not be necessary:

string json = ConvertToJson(acme,  ... etc

sergiopereira wrote re: Designing With Lambdas - Part IV
on 05-12-2008 12:03 PM

@James

Correct. It's just that I like to be explicit when calling generic methods.

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)