Introduction
.NET 3.5 has many new cool features. One of them being lambda expressions, which can cleanup a lot of code, and really push higher-order functions to a new level.
Background
Lambda Expression in 3.5
Basically, lambda expressions provide a way to anonymously implement delegates using Type Inferencing.
ublic void performOnAllMembers(Action<Foo> action) {
foreach( Foo foo in datastructure ) {
action(foo);
}
}
performOnAllMembers(delegate(Foo foo) {
foo.bar();
});
performOnAllMembers( foo => foo.bar() );
Lambda expressions are very common in functional programming languages such as Scheme, ML, F# etc., and is a fundamental programming concept. I am actually a bit surprised it took 7 years for Microsoft to adopt it.
The article will try to apply lambda expressions to implement a converter from an underlying data source (IEnumerable<T>
) and apply a list of transformations based on a List of Mappings that we call (Transformer<F,T>
). In this example, the final data source is a DataTable
.
The scenario
One way to convert a IEnumerable
data source such as a Business Object Collection is to manually create the data table, add all the necessary columns and data types, then look through the data source and manually provide a mapping of the data to column rows. So for example, you might have something like this...
...
List<Employee> emps = getEmployees();
DataTable dt = new DataTable("Employee");
dt.Columns.Add(New DataColumn("FirstName", typeof(string)));
dt.Columns.Add(New DataColumn("Address Info", typeof(string)));
dt.Columns.Add(New DataColumn("Salary", typeof(double)));
dt.Columns.Add(New DataColumn("Performance", typeof(double)));
dt.Columns.Add(New DataColumn("Cool", typeof(bool)));
foreach(Employee e in )
{
DataRow dr = dt.NewRow();
dr["FirstName"] = e.Contact.FirstName;
dr["Address Info"] = e.Address;
dr["Performance"] = getPerformance(e.ID);
if(e.FirstName.Equals("Yang"))
dr["Cool"] = true;
else
dr["Cool"] = false;
dt.Rows.Add(dr);
}
return dt;
...
This approach will get the job done; however, for every data conversion you might have from a complex business object with lots of columns, you might pull some hair out writing this kind of code over and over again.
Another approach is to create a single converter<F,T>
that converts a type F
such as Employee
into a type T
such as a DataRow
. However, you will still be writing a lot of hardcoded column and result mappings.
The solution
You guessed it! Here is where lambda expressions are used. Similar to a PropertyPath
in a WPF binding property, we can create a lambda expression that describes the transformation of a object of type F
to type T
.
For example:
dr["FirstName"] = e.Contact.FirstName;
is applied on an object e
of Employee
, and returns a string which is what the column is expecting (f:Employee->String
). Similarly, the other column mappings can all be described as a functional transformation from an Employee
.
delegate string EmployeeToString(Employee emp);
var FirstNameTransformer = e => { return e.Contact.FirstName; }
Func<Employee,string>
LastNameTransformer = (e => { return e.Contact.LastName; });
Similarly, each column mapping can be represented by a transformation. This means that for each column, we need a transformation function Func<T,F>
. In the case of a property, we already have such a function, so we do not need to create a new one.
...
private Func<F,T> GetPropertyDelegate<F,T> (string PropertyName)
{
return (Func<F, T>)Delegate.CreateDelegate(typeof(Func<F, T>),
typeof(F).GetProperty(PropertyName, typeof(T)).GetGetMethod());
}
return GetPropertyDelegate<Employee,string> ("Address");
...
Putting it together
We can use an interface to store a Mapping Transformation which contains the source data type F
as a generic type and then a transform function that will basically transform that type to a desired type.
public interface ITransformer<F>
{
Type DataType { get; }
string Caption { get; }
DataColumn AsColumn { get; }
object transform(F value);
}
Now, let's look at an implementation of this:
public class Transformer<F, T> : ITransformer<F>
{
private readonly string _caption;
private readonly Func<F, T> _tranfer;
private DataColumn _column;
#region properties
public Type DataType
{
get
{
return typeof(T);
}
}
public string Caption
{
get
{
return this._caption;
}
}
public DataColumn AsColumn
{
get
{
if (this._column == null)
this._column = new DataColumn(this.Caption, this.DataType);
return this._column;
}
}
#endregion
#region constructors
public Transformer(string Caption, string PropertyName)
: this(Caption,
(Func<F, T>)Delegate.CreateDelegate(typeof(Func<F, T>),
typeof(F).GetProperty(PropertyName, typeof(T)).GetGetMethod()))
{
}
public Transformer(string Caption, Func<F, T> transfer)
{
this._caption = Caption;
this._tranfer = transfer;
}
#endregion
public object transform(F source)
{
return this._tranfer.Invoke(source);
}
}
public class DataTableMapper<T>
{
public static DataTable ConvertToDataTable(IEnumerable<T> list,
ITransformer<T>[] Mappings)
{
if (Mappings == null || list == null) return null;
DataTable dt = new DataTable();
dt.BeginLoadData();
Type tType = typeof(T);
foreach (ITransformer<T> m in Mappings)
dt.Columns.Add(m.AsColumn);
foreach (T beo in list)
{
DataRow dr = dt.NewRow();
foreach (ITransformer<T> m in Mappings)
dr[m.Caption] = m.transform(beo);
dt.Rows.Add(dr);
}
dt.EndLoadData();
return dt;
}
}
How to use
To use this, we simply declare an ITransformer<T>
mapping array which is used to build our data table.
Each ITransformer
maps to a new DataColumn
in the table.
...
List<Employee> emps = getEmployees();
var mappings = new ITransformer<Employee>[] {
new Transformer<Employee, String> ("First Name", e => { return e.Contact.FirstName; }),
new Transformer<Employee, String> ("Address Info", "Address"),
new Transformer<Employee, double> ("Performance", e => { getPerformance(e.ID); }),
new Transformer<Employee, bool> ("Cool", e => { return e.FirstName.Equals("Yang"); }),
DataTable dt = DataTableMapper<Employee>.ConvertToDataTable(emps, mappings);
return dt;
...
I hope you guys enjoyed this article. Please submit your feedback and comments.
Points of interest
This is just a start of using lambda to extract higher-order functions. We can potentially incorporate some LINQ Expression into the lambda expression for even more powerful joins and queries.
The only performance impact of this code is creating the lambda function; it does use Reflection to retrieve the property method in one constructor type. However, this is a tiny impact.
History
- March 13, 2009 - First draft created!