Click here to Skip to main content
15,883,901 members
Articles / Programming Languages / C# 4.0

Flexpressions

Rate me:
Please Sign up or sign in to vote.
5.00/5 (19 votes)
10 Oct 2012CPOL10 min read 39.9K   507   47  
An intuitive-fluent API for generating Linq Expressions.
//  Flexpressions
//  Copyright © 2012 Andrew Rissing
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights 
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//  of the Software, and to permit persons to whom the Software is furnished to do so, 
//  subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all 
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//  PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//  FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Flexpressions.Extensions;
using Flexpressions.Interfaces;
using Microsoft.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FlexpressionsTest
{
	/// <summary>
	/// The Utility class contains core unit test functionality for all of the unit tests.
	/// </summary>
	public static class Utility
	{
		/// <summary>
		/// Populates a dictionary with all possible variants of an <see cref="Action"/> with <typeparamref name="T"/> arguments, supplying
		/// these values to a interceptor method that will collapse all of these values into an array of <typeparamref name="T"/>.
		/// </summary>
		/// <typeparam name="T">The type of the parameters to use for the action.</typeparam>
		/// <param name="interceptor">The action that will combine all the parameters into a single input.</param>
		/// <returns>A dictionary with all possible variants of an action.</returns>
		public static Dictionary<Type, Expression> CreateActions<T>(Action<T[]> interceptor)
		{
			return new Dictionary<Type, Expression>()
			{
				{ typeof(Expression<Action>), ((Expression<Action>)(() => interceptor(new T[] {  }))) },
				{ typeof(Expression<Action<T>>), ((Expression<Action<T>>)((p1) => interceptor(new T[] { p1 }))) },
				{ typeof(Expression<Action<T, T>>), ((Expression<Action<T, T>>)((p1, p2) => interceptor(new T[] { p1, p2 }))) },
				{ typeof(Expression<Action<T, T, T>>), ((Expression<Action<T, T, T>>)((p1, p2, p3) => interceptor(new T[] { p1, p2, p3 }))) },
				{ typeof(Expression<Action<T, T, T, T>>), ((Expression<Action<T, T, T, T>>)((p1, p2, p3, p4) => interceptor(new T[] { p1, p2, p3, p4 }))) },
				{ typeof(Expression<Action<T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T>>)((p1, p2, p3, p4, p5) => interceptor(new T[] { p1, p2, p3, p4, p5 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6) => interceptor(new T[] { p1, p2, p3, p4, p5, p6 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15 }))) },
				{ typeof(Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T>>), ((Expression<Action<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16 }))) },
			};
		}
		/// <summary>
		/// Populates a dictionary with all possible variants of an Func with <typeparamref name="T"/> arguments and a return of <typeparamref name="R"/>, supplying
		/// these values to a interceptor method that will collapse all of these values into an array of <typeparamref name="T"/>.
		/// </summary>
		/// <typeparam name="T">The type of the parameters to use for the action.</typeparam>
		/// <typeparam name="R">The type of the return value from the Func.</typeparam>
		/// <param name="interceptor">The action that will combine all the parameters into a single input.</param>
		/// <returns>A dictionary with all possible variants of a func.</returns>
		public static Dictionary<Type, Expression> CreateFuncs<T, R>(Func<T[], R> interceptor)
		{
			return new Dictionary<Type, Expression>()
			{
				{ typeof(Expression<Func<R>>), ((Expression<Func<R>>)(() => interceptor(new T[] {  }))) },
				{ typeof(Expression<Func<T, R>>), ((Expression<Func<T, R>>)((p1) => interceptor(new T[] { p1 }))) },
				{ typeof(Expression<Func<T, T, R>>), ((Expression<Func<T, T, R>>)((p1, p2) => interceptor(new T[] { p1, p2 }))) },
				{ typeof(Expression<Func<T, T, T, R>>), ((Expression<Func<T, T, T, R>>)((p1, p2, p3) => interceptor(new T[] { p1, p2, p3 }))) },
				{ typeof(Expression<Func<T, T, T, T, R>>), ((Expression<Func<T, T, T, T, R>>)((p1, p2, p3, p4) => interceptor(new T[] { p1, p2, p3, p4 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, R>>)((p1, p2, p3, p4, p5) => interceptor(new T[] { p1, p2, p3, p4, p5 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6) => interceptor(new T[] { p1, p2, p3, p4, p5, p6 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15 }))) },
				{ typeof(Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, R>>), ((Expression<Func<T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, R>>)((p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16) => interceptor(new T[] { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16 }))) },
			};
		}
		/// <summary>
		/// Makes the method concrete (if it is generic).
		/// </summary>
		/// <typeparam name="T">The type to substitute into each generic parameter of the method.</typeparam>
		/// <typeparam name="TLast">The type of the last type in the generic method (used for the generic return type of Funcs).</typeparam>
		/// <param name="methodInfo">The <see cref="MethodInfo"/> to make concrete.</param>
		/// <returns>The concrete <see cref="MethodInfo"/> ready for execution.</returns>
		public static MethodInfo MakeMethodConcrete<T, TLast>(this MethodInfo methodInfo)
		{
			if (methodInfo.IsGenericMethod)
			{
				var replacementType = typeof(T);
				var lastReplacementType = typeof(TLast);
				var genericArguments = methodInfo.GetGenericArguments();

				Type[] typeArray = new Type[genericArguments.Length];

				for (int i = 0; i < typeArray.Length; ++i)
				{
					var typeConstraints = genericArguments[i].GetGenericParameterConstraints();

					if (typeConstraints.Length > 0)
						typeArray[i] = typeConstraints[0];
					else if (i == (typeArray.Length - 1))
						typeArray[i] = lastReplacementType;
					else
						typeArray[i] = replacementType;
				}

				methodInfo = methodInfo.MakeGenericMethod(typeArray);
			}

			return methodInfo;
		}
		/// <summary>
		/// Tests the provided expression to ensure the reverse engineered expression using the <see cref="ExpressionConverter"/>
		/// is equivalent and valid.
		/// </summary>
		/// <param name="input"></param>
		public static void TestConverter(this Expression input)
		{
			// Compile the Expression from CSharp and retrieve it back.
			var reverseEngineered = Utility.CompileCSharpStringFromExpression(input);

			Utility.AssertCSharpStringsAreEqual(input, reverseEngineered);
		}
		/// <summary>
		/// Tests the provided expression to ensure the compiled expression produces the expected results from the provided inputs
		/// and the reverse engineered expression using the <see cref="ExpressionConverter"/> is equivalent and valid.
		/// </summary>
		/// <typeparam name="T">The type of the expected result.</typeparam>
		/// <param name="input">The input <see cref="Expression"/>.</param>
		/// <param name="expectedResult">The expected result.</param>
		/// <param name="args">The arguments for the expression.</param>
		public static void TestExpression<T>(this Expression input, T expectedResult, params object[] args)
		{
			var type = typeof(T);

			// Compile the Expression from CSharp and retrieve it back.
			var reverseEngineered = Utility.CompileCSharpStringFromExpression(input);

			Utility.AssertCSharpStringsAreEqual(input, reverseEngineered);

			var inputCompiled = ((LambdaExpression)input).Compile();
			var reverseEngineeredCompiled = ((LambdaExpression)reverseEngineered).Compile();

			var inputResult = (T)inputCompiled.DynamicInvoke(args);
			var reverseEngineeredResult = (T)reverseEngineeredCompiled.DynamicInvoke(args);

			if (typeof(ICollection).IsAssignableFrom(type))
			{
				CollectionAssert.AreEqual((ICollection)expectedResult, (ICollection)inputResult);
				CollectionAssert.AreEqual((ICollection)expectedResult, (ICollection)reverseEngineeredResult);
			}
			else if (type.IsArray)
			{
				var baseType = type.GetElementType();

				var arrayExpected = (Array)(object)expectedResult;
				var arrayInput = (Array)(object)inputResult;
				var arrayReverseEngineered = (Array)(object)reverseEngineeredResult;

				Assert.AreEqual<int>(arrayExpected.Length, arrayInput.Length);
				Assert.AreEqual<int>(arrayExpected.Length, arrayReverseEngineered.Length);

				for (int i = 0; i < arrayExpected.Length; ++i)
				{
					Assert.AreEqual(arrayExpected.GetValue(i), arrayInput.GetValue(i));
					Assert.AreEqual(arrayExpected.GetValue(i), arrayReverseEngineered.GetValue(i));
				}
			}
			else
			{
				Assert.AreEqual<T>(expectedResult, inputResult);
				Assert.AreEqual<T>(expectedResult, reverseEngineeredResult);
			}
		}
		/// <summary>
		/// Validates the <see cref="InputCase"/>s for the particular object by using reflection to call all of its methods.
		/// </summary>
		/// <typeparam name="T">The <see cref="Type"/> of the <see cref="IFlexpression"/> object.</typeparam>
		/// <param name="flexpression">The flexpression instance.</param>
		/// <param name="inputs">The collection of <see cref="InputCase"/>s.</param>
		public static void ValidateInputCase<T>(this T flexpression, params InputCase[] inputs) where T : IFlexpression
		{
			var flexpressionType = typeof(T);
			var lookup = inputs.ToLookup(x => x.Name);

			foreach (var method in flexpressionType.GetMethods())
			{
				foreach (var inputCase in lookup[method.Name])
				{
					if (inputCase.Arguments.Length == method.GetParameters().Length)
					{
						var methodToInvoke = method.MakeMethodConcrete<int, int>();
						var failureTriggered = false;
						var methodToInvokeParameters = methodToInvoke.GetParameters();
						var argumentsToUse = new object[methodToInvokeParameters.Length];

						// Verify all the argument types match up.
						for (int i = 0; i < methodToInvokeParameters.Length; ++i)
						{
							if ((inputCase.Arguments[i] != null)
								&& !methodToInvokeParameters[i].ParameterType.IsAssignableFrom(inputCase.Arguments[i].GetType())
								&& typeof(Expression).IsAssignableFrom(methodToInvokeParameters[i].ParameterType))
							{
								var delegateType = methodToInvokeParameters[i].ParameterType.GetGenericArguments()[0];
								var genericArguments = delegateType.GetGenericArguments();
								var numberOfParameters = (delegateType.GetMethod("Invoke").ReturnType == typeof(void)) ? genericArguments.Length : genericArguments.Length - 1;

								// If there is an argument type mismatch for expressions, build a valid expression from the provided argument.
								argumentsToUse[i] = Expression.Lambda
								(
									delegateType,
									((LambdaExpression)inputCase.Arguments[i]).Body,
									genericArguments.Take(numberOfParameters).Select(x => Expression.Parameter(x))
								);
							}
							else
							{
								argumentsToUse[i] = inputCase.Arguments[i];
							}
						}

						try
						{
							methodToInvoke.Invoke(flexpression, argumentsToUse);
						}
						catch (TargetInvocationException e)
						{
							failureTriggered = (e.GetBaseException().GetType() == inputCase.ExpectedExceptionType);
						}
						finally
						{
							Assert.IsTrue(failureTriggered, string.Format("InputCase failed (Name: {0}, Exception: {1})", inputCase.Name, inputCase.ExpectedExceptionType.GetFriendlyName()));
						}
					}
				}
			}
		}

		/// <summary>
		/// Asserts that the strings produced by the two provided expressions are equal.
		/// </summary>
		/// <param name="left">The left <see cref="Expression"/> to compare.</param>
		/// <param name="right">The right <see cref="Expression"/> to compare.</param>
		private static void AssertCSharpStringsAreEqual(Expression left, Expression right)
		{
			var inputCSharp = Utility.StripCommentsOffBeginning(left.ToCSharpString(true));
			var reverseEngineeredCSharp = Utility.StripCommentsOffBeginning(right.ToCSharpString(true));

			Assert.AreEqual<string>(inputCSharp, reverseEngineeredCSharp);
		}
		/// <summary>
		/// Takes the provided expression, constructs the equivalent C# string, and then converts it back to an <see cref="Expression"/>.
		/// </summary>
		/// <param name="expression">The <see cref="Expression"/> to work on.</param>
		/// <returns>The <see cref="Expression"/> returned from the compiled C# code.</returns>
		private static Expression CompileCSharpStringFromExpression(Expression expression)
		{
			string originalCSharp = expression.ToCSharpString(true);
			string code = string.Format(@"
public static class __CompiledCode__
{{
	public static System.Linq.Expressions.Expression Run()
	{{
{0}

		return expression;
	}}
}}",
			originalCSharp);

			var parameters = new CompilerParameters();
			parameters.ReferencedAssemblies.Add("System.dll");
			parameters.ReferencedAssemblies.Add("System.Core.dll");
			parameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
			parameters.ReferencedAssemblies.Add("FlexpressionsTest.dll");
			parameters.GenerateInMemory = true;

			var csProvider = new CSharpCodeProvider();
			var results = csProvider.CompileAssemblyFromSource(parameters, code);
			var assembly = results.CompiledAssembly;
			var compiledType = assembly.GetType("__CompiledCode__");

			var resultingExpression = compiledType.GetMethod("Run").Invoke(null, null) as Expression;

			return resultingExpression;
		}
		/// <summary>
		/// Strips the comments off beginning of the code snippet.
		/// </summary>
		/// <param name="code">The C# code to operate on.</param>
		/// <returns>The cleaned up C# code.</returns>
		private static string StripCommentsOffBeginning(string code)
		{
			using (var sr = new StringReader(code))
			{
				while (sr.Peek() == '/')
					sr.ReadLine();

				return sr.ReadToEnd();
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Since I've begun my profession as a software developer, I've learned one important fact - change is inevitable. Requirements change, code changes, and life changes.

So..If you're not moving forward, you're moving backwards.

Comments and Discussions