Showing posts with label linq. Show all posts
Showing posts with label linq. Show all posts

Monday, 24 June 2013

Anonymous function in anger with Memoize

About a year ago I blogged about anonymous functions, lambdas and delegates. I've had cause to use these principals in anger to create a memoize function; not exactly original but interesting never the less. I made this memoize function, which takes an anonynous function that takes 2 parameters.

static Func<T1, T2, TResult> Memoize<T1, T2, TResult>(Func<T1, T2, TResult> f)
{
    var dict = new Dictionary<Tuple<T1, T2>, TResult>();
    return (a, b) =>
    {
        TResult result;
        if (!dict.TryGetValue(Tuple.Create(a, b), out result))
        {
            result = f(a, b);
            dict.Add(Tuple.Create(a, b), result);
        }
        return result;
    };
}

N.B. It uses the .net4 Tuple class to store the 2 parameters, for the sake a simplicity I haven't generalized this for 1 or > 2 parameters, or made it an extension method. You then need to wrap the function you want memoizing.

var myFunc1 = Memoize((Func<int, int, MyClass1>)Some.Dot.Namespace.Func1);
var myNewClass = new MySomethingClass();
var myFunc2 = Memoize((Func<string, string, MyClass2>)myNewClass.Func2);

while(true)
{
    var result = myFunc1(1,2);
    var result2 = myFunc2("1","2");
}

The results of the memoized functions are cached inside the function calls, leading to better performance if the function is a called with the same parameters many times in the loop.

Thursday, 5 July 2012

C# Anonymous, Lambdas, Delegates and Expressions...phew.

C# and by extension any .net language has complex support for both anonymous functions and lambdas. I found it quite useful to have a basic reference for these; so here it is.
// Anonymous function using the delegate keyword.
Func<string> anon = delegate() { return "x"; });

// Anonymous function from lambda.
Func<string> lambda = () => "x";

// Same but using explicit typing.
var anon2 = new Func<string>(delegate() { return "x"; });
The function references (anon, lambda, anon2) are known in .net as delegates which is nomenclature for function pointer. Because C# is statically typed we have to explicitly type the delegate, hence 'Func'. Func is a generic delegate type: a predefined delegate, rather than having to specify it constantly. We could explicity define the delegate like this.
// Define delegate.
delegate int myDelegate(int i);

// Assign delegate.
myDelegate anon = delegate(int i) { return i };
myDelegate lambda = x => x + 1;

Linq Expressions is where things start to get more complex. Linq expressions allow lambda functions to be reflected and turned into data instead of code, allowing you to change the function. A typical change would be from a .net lambda function to an SQL statement.
// Expression
Expression<Func<string>> exp = () => "x";

// Can't be done.
// Expression<Func<string>> exp2 = delegate() { return "x"; };
However note that Linq Expressions cannot be created using anonymous functions.

OK if you followed all that....lambda can also be written with a statement body rather than an expression body as we have seen so far. Statement bodies allow more complex multiline functions.
// Statement lambda
Func<string> lambda = () => { return "x"; };
Statement and expression body lambdas are functionaly identical, but they are different. Statement body lambdas cannot be made into expression trees. In practice this means that certain linq providers, such as Linq2Sql cannot use lambda functions created with statement bodies.
A lambda expression with a statement body cannot be converted to an expression tree
One final quirk I'll mention is that once you have an explicit Expression it can be used on an IQueryable, but not a standard IEnumerable. You have to 'unwrap' it, converting it back to a standard Func.
// OK, compiler sorts it out
var l0 = new int[] { 1, 2, 3 }.Where(x => x < 2);
var l1 = new int[] { 1, 2, 3 }.AsQueryable<int>().Where(x => x < 2);

// Force exp to be an Expression
Expression<Func<int, bool>> exp = x => x < 2;

// Doesn't work - invalid arguments
//var l2 = new int[] { 1, 2, 3 }.Where(exp);

// Works on IQueryable
var l3 = new int[] { 1, 2, 3 }.AsQueryable<int>().Where(exp);

// Convert Expression back to standard Func to make it work.
var l4 = new int[] { 1, 2, 3 }.Where(exp.Compile());