One of the cooler features of c# 3.0 are expression trees. A quick intro can be found here .
Now the fact that you can compile a function at runtime means that you can build a dynamic language on top of Linq Expressions.
Here’s a simple dynamic “XML Language” written using Expression Trees.
Basically, the idea here is to have a one-to-one mapping between your XML expression and the Linq EXpressions themselves.
For example,
<Expression FunctionName="Constant" Type="System.String" Value="someval" />
would create an Linq Expression of type
Expression.Constant("someval",Type.GetType(string));
Here’s a related test for the mapping:
[TestMethod]
public void GetConstantTest()
{
XMLExpressionProvider expr = new XMLExpressionProvider();
XElement elem = expr.BuildConstant(typeof(int).ToString(), "1");
Assert.AreEqual("1", expr.GetConstant(elem));
}
and related implementations:
public XElement BuildConstant(string type, string value)
{
XElement elem = new XElement("Expression");
elem.Add(new XAttribute("FunctionName", "Constant"));
elem.Add(new XAttribute("Type", type));
elem.Add(new XAttribute("Value", value));
return elem;
}
public object GetConstant(XElement elem)
{
return elem.Attribute("Value").Value;
}
Binary operations can also be mapped similarly.
Here we’re creating a new Add Expression with the constants 1 and 2. This actually creates a xml representation for the Add Operation.
[TestMethod]
public void BuildExpressionTest()
{
XMLExpressionProvider expr = new XMLExpressionProvider();
XElement xelm = expr.BuildExpression("Add", new[] {
expr.BuildConstant(typeof(int).ToString(),"1"),
expr.BuildConstant(typeof(int).ToString(),"2")});
Assert.AreEqual(xelm.ToString(SaveOptions.DisableFormatting),
"<Expression FunctionName=\"Add\"><Expression FunctionName=\"Constant\" Type=\"System.Int32\" Value=\"1\" /><Expression FunctionName=\"Constant\" Type=\"System.Int32\" Value=\"2\" /></Expression>");
}
Basically the XML Represenation would look like
<Expression FunctionName="Add">
<Expression FunctionName="Constant" Type="System.Int32" Value="1" />
<Expression FunctionName="Constant" Type="System.Int32" Value="2" />
</Expression>
In the test below, we’re actually creating a XML Representation for a GreaterThan expression and executing it with different parameters.
[TestMethod]
public void ExpressionTest()
{
XMLExpressionProvider expr = new XMLExpressionProvider();
ParameterExpression p = Expression.Parameter(typeof(int), "x");
XElement elem = expr.BuildExpression("GreaterThan", new[] { expr.BuildConstant(typeof(int).ToString(), "5"), expr.BuildParameter(0) });
Expression expression= expr.ExpressionFromXElement(elem, new [] {p});
Expression<Func<int, bool>> e = Expression.Lambda<Func<int, bool>>(expression,new []{p});
Console.WriteLine(e.ToString());
Assert.IsTrue(e.Compile().Invoke(4));
Assert.IsFalse(e.Compile().Invoke(7));
}
The heart of the implementation lies in the method ExpressionFromXElement which is implemented as
public Expression ExpressionFromXElement(XElement elem, ParameterExpression [] p)
{
if (IsExpression(elem))
{
switch (GetFunctionName(elem))
{
case "Or":
return Expression.Or(ExpressionFromXElement(GetArguments(elem)[0], p), ExpressionFromXElement(GetArguments(elem)[1],p));
case "And":
return Expression.And(ExpressionFromXElement(GetArguments(elem)[0], p), ExpressionFromXElement(GetArguments(elem)[1], p));
case "LessThan":
return Expression.LessThan(ExpressionFromXElement(GetArguments(elem)[0], p), ExpressionFromXElement(GetArguments(elem)[1], p));
case "GreaterThan":
return Expression.GreaterThan(ExpressionFromXElement(GetArguments(elem)[0], p), ExpressionFromXElement(GetArguments(elem)[1], p));
case "Equal":
return Expression.Equal(ExpressionFromXElement(GetArguments(elem)[0], p), ExpressionFromXElement(GetArguments(elem)[1], p));
case "Condition":
return Expression.Condition(ExpressionFromXElement(GetArguments(elem)[0], p), ExpressionFromXElement(GetArguments(elem)[1], p), ExpressionFromXElement(GetArguments(elem)[2], p));
case "Parameter":
return p[Convert.ToInt32(elem.Attribute("Index").Value)];
case "Constant":
return Expression.Constant(((object)GetConstant(elem)),Type.GetType(elem.Attribute("Type").Value));
case "Member":
return Expression.PropertyOrField(ExpressionFromXElement(GetArguments(elem)[0], p), elem.Attribute("FieldName").Value);
case "IsEmptyOrNull":
return Expression.Or(
Expression.Equal(ExpressionFromXElement(GetArguments(elem)[0], p), Expression.Constant(String.Empty)),
Expression.Equal(ExpressionFromXElement(GetArguments(elem)[0], p), Expression.Constant(null)));
default: throw new Exception();
}
}
return null;
}
You can also do member accesses:
[TestMethod]
public void MemberAccessTest()
{
XMLExpressionProvider expr = new XMLExpressionProvider();
ParameterExpression p = Expression.Parameter(typeof(string), "str");
XElement elem = expr.BuildMemberExpr("Length", new []{expr.BuildParameter(0)});
Expression expression = expr.ExpressionFromXElement(elem, new[] { p });
var e = Expression.Lambda<Func<string, int>>(expression, new[] { p });
Console.WriteLine(e.ToString());
Assert.AreEqual(6, e.Compile().Invoke("string"));
Assert.AreEqual(3, e.Compile().Invoke("str"));
}
[TestMethod]
public void BuildMemberTest()
{
XMLExpressionProvider expr = new XMLExpressionProvider();
Assert.AreEqual("<Expression FunctionName=\"Member\" FieldName=\"MaxValue\"><Expression FunctionName=\"Parameter\" Index=\"0\" /></Expression>", expr.BuildMemberExpr("MaxValue", new []{expr.BuildParameter(0)} ).ToString(SaveOptions.DisableFormatting));
}
The examples given here should give you an idea of how it works and the possiblities that exist.