Last time I was writing about a utility class called ReflectionHelper, this time I'll show you two real scenarios where this class or just expression tree have been used.
For last couple of months I've been working on a project built on the top of Workflow Foundation. Now we have C#3.0 many thinks can be done smarter, simpler, quicker, better or ... more cool ;) Let's look at a simple workflow class:
class MyWorkflow : SequentialWorkflowActivity { public int MyInt { get; set; } public string MyString { get; set; } public MyWorkflow() { CodeActivity codeActivity = new CodeActivity("codeActivity1"); codeActivity.ExecuteCode += delegate { Console.WriteLine("MyInt : " + MyInt); Console.WriteLine("MyString : " + MyString); MyInt++; MyString = "yo"; }; Activities.Add(codeActivity); } }
I know that not all of you had the opportunity to use WF so I'll try to give you some basics. The most elementary component in WF is an activity. We can say that activity is just a class with 'Excute' method responsible for doing some action. Workflow process is also an activity containg another activities which are executed one by one (that's why it derives from SequentialWorkflowActivity). Our workflow has only one activity CodeActivity which raises ExecuteCode event when it's executed. Additionally our process manipulates its state stored in properties MyInt and MyString. Let's see how to start this process and initialize its state:
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime()) { var arguments = new Dictionary<string, object>() { {"MyString", "hello"}, {"MyInt", Math.Max(5, 10)}, }; WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(MyWorkflow), arguments); instance.Start(); }
The problem is that method CreateWorkflow takes a dictionary where the key is a property name and the value is a value of the property. But what if someone changes the name or the type of the property someday ? The code will compile fine but at runtime we will get an exception. Since we have C#3.0 we can very easily resolve these two problems.
var arguments = new WorkflowArguments<MyWorkflow>() { { w => w.MyString, "hello" }, { w => w.MyInt, Math.Max(5,10) } }; WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(MyWorkflow), arguments); public class WorkflowArguments<TWorkflow> : Dictionary<string,object> { public WorkflowArguments<TWorkflow> Add<T>(Expression<Func<TWorkflow, T>> property, T propertyValue) { var propertyInfo = Blog.Post002.ReflectionHelper.GetProperty<TWorkflow,T>(property); Add(propertyInfo.Name, propertyValue); return this; } }
The same technique we be used to process parameters after execution.
static void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e) { Console.WriteLine("workflowRuntime_WorkflowCompleted"); var parameters = new WorkflowOutputParameters<MyWorkflow>(e.OutputParameters); Console.WriteLine(parameters.GetParameter( w => w.MyInt)); Console.WriteLine(parameters.GetParameter( w => w.MyString)); } public class WorkflowOutputParameters<TWorkflow> { private Dictionary<string, object> Parameters { get; set; } public WorkflowOutputParameters(Dictionary<string, object> parameters) { Parameters = parameters; } public T GetParameter<T>(Expression<Func<TWorkflow, T>> property) { var propertyInfo = Blog.Post002.ReflectionHelper.GetProperty<TWorkflow, T>(property); return (T)Parameters[propertyInfo.Name]; } }
The second example is a Mapper class. It's a quite common scenario when we map instance of class A into instance of class B. For example, we very often load some kind of DAL entity from database into memory then we map it to some kind of business entity. In many cases the shape of both types if very similar, they have the same properties/fields so the mapping code is very simple. It just transfers values of properties or fields from one object to another. Mapper class is a very simple class giving us the ability to record the mapping of two types to each other. Next we can execute mapping process specifying two instances of those classes and the direction of mapping. Lets assume that we have two types:
class CustomerDal { public int Id { get; set; } public string Name { get; set; } public double Age { get; set; } public char Gender { get; set; } public string City { get; set; } public string Street { get; set; } } class CustomerBO { public int Id { get; private set; } // only getter public string Name { get; set; } // exactly the same name public double Age; // field public char Sex { get; set; } // another name public string Address { get; set; } // more complicated mapping }
Now we want to map an instance of CustomerDal type to instance of CustomerBo. Firstly we need to define how the properties of types should be transformed. Once we have it we can start mapping.
var m1 = new Mapper<CustomerDal, CustomerBO> // A type, B type { {a => a.Id, b => b.Id, Direction.A2B}, // member of type A, member of type B, mapping direction {a => a.Name, b => b.Name}, // by default map in both directions {a => a.Age, b => b.Age}, {a => a.Gender, b => b.Sex}, { (a, b, d) => b.Address = a.City + " " + a.Street , Direction.A2B} // code snippet executed during mapping }; var customerDal = new CustomerDal { Id = 1, Name = "Michael", City = "Chicago", Street = "W Washington", Age = 25, Gender = 'M'}; var customerBO = new CustomerBO(); m1.Map(customerDal, customerBO); customerBO.Name = customerBO.Name + "!"; customerBO.Sex = 'F'; customerBO.Age = customerBO.Age + 1; m1.Map(customerBO, customerDal);
Again, instead of specifying mapped properties in code as string literals we use expression trees. Using expression trees is safer, cleaner and much more powerful. I showed you just two examples of real life application of ExpressionTrees that goes for beyond Linq. You saw the potential. Now imagine what you can do in your project with it.
The source code for both examples you can be found here.