A great thing about .NET Expression Trees is you can compile them at runtime, and they’ll execute as fast as any .NET code.  However, while building and compiling a tree is pretty quick, it’s best if you only need to do it once.

The way you’d usually do that is cache the compiled delegate somewhere.  If you just want to create it and use it repeatedly in the same code block, just hold the delegate in a local variable:

void Main()
{
    var dump = MakeDumpDelegate<DateTime>();

    for ( var yearOffset = 0; yearOffset < 5; ++ yearOffset )
    {
        Console.WriteLine( string.Join( " | ", dump( DateTime.Now.AddYears( yearOffset ) ) ) );
    }
}

Func<TModel, object[]> MakeDumpDelegate<TModel>()
{
    var modelParam = Expression.Parameter( typeof( TModel ), "model" );
    var properties = typeof( TModel ).GetProperties( BindingFlags.Public | BindingFlags.Instance ).OrderBy( p => p.Name );

    var lambda = Expression.Lambda<Func<TModel, object[]>>(
        Expression.NewArrayInit(
            typeof( object ),
            from p in properties
            select Expression.Convert( Expression.MakeMemberAccess( modelParam, p ), typeof( object ) ) ),
        modelParam );

    return lambda.Compile();
}

Here MakeDumpDelegate creates a delegate that takes an object of type TModel, and returns an object array of its alphabetically sorted property values.  In the Main method we’re creating a delegate based on the DateTime type, running it 5 times and spitting out the values.

What if there are a bunch of classes we want to run delegates for?  We could move the code out of Main and call it wherever we need it:

void Main()
{
    var dates = Enumerable.Range( 0, 5 ).Select( yo => DateTime.Now.AddYears( yo ) );

    foreach ( var d in DumpProperties( dates ) )
    {
        Console.WriteLine( d );
    }
}

IEnumerable<string> DumpProperties<TModel>( IEnumerable<TModel> models )
{
    var dump = MakeDumpDelegate<TModel>();

    return from m in models select string.Join( " | ", dump( m ) );
}

This works great, and we can call DumpProperties with any collection we like.  The problem is, we’re building and compiling the expression tree every time we use it.  This is pretty inefficient, so let’s add a cache of delegates for each type.

But wait…  Let’s say we want to hit these things from multiple threads.  We’re probably going to need a static Dictionary<Type, delegate> to hold onto them, and we’ll need to provide some locking to stop the threads messing us up.  This is too complicated to be good.

Instead, we’ll use a generic class and let .NET help us out.  When you declare a generic class, each specialization of that class gets its own copy of its static members:

class MyClass<T>
{
    public static T MyStaticValue { get; set; }
}

So if you access MyClass<int>.MyStaticValue or MyClass<double>.MyStaticValue, they’re obviously different instances (one is int and the other double).  We can use this to hold onto our delegate:

void Main()
{
    var dates = Enumerable.Range( 0, 5 ).Select( yo => DateTime.Now.AddYears( yo ) );

    foreach ( var d in DumpProperties( dates ) )
    {
        Console.WriteLine( d );
    }
}

IEnumerable<string> DumpProperties<TModel>( IEnumerable<TModel> models )
{
    var dump = DumpDelegate<TModel>.Dump;

    return from m in models select string.Join( " | ", dump( m ) );
}

class DumpDelegate<TModel>
{
    public static Func<TModel, object[]> Dump { get; private set; }

    static DumpDelegate()
    {
        var modelParam = Expression.Parameter( typeof( TModel ), "model" );
        var properties = typeof( TModel ).GetProperties( BindingFlags.Public | BindingFlags.Instance ).OrderBy( p => p.Name );
    
        var lambda = Expression.Lambda<Func<TModel, object[]>>(
            Expression.NewArrayInit(
                typeof( object ),
                from p in properties
                select Expression.Convert( Expression.MakeMemberAccess( modelParam, p ), typeof( object ) ) ),
            modelParam );

        Dump = lambda.Compile();
    }
}

What’s great about this is .NET guarantees the static constructor will only be called once, and in a completely thread-safe manner.  Since DumpDelegate is a generic class based on TModel, each specialization will only be initialized once – and only on first use.  Our threading headache has just disappeared, and we’re building and compiling the tree just once for each model type Smile

kick it on DotNetKicks.com

About these ads