Mutable Linq Expressions

time to read 3 min | 499 words

As you probably know, System.Linq.Expression is considered to be immutable. This is a major pain when it comes to working with them, and many Linq providers end up creating a parallel hierarchy of expressions that are mutable.

What I am going to show you now is probably going to get some people in Microsoft pretty angry with me, but hey, if they used ReSharper, they wouldn’t have this problem. In a true TDD fashion, let us start with a failing test:

var constant = Expression.Constant(5);
var captureReference = constant;

// put something here that may not change the captureReference variable

Debug.Assert(ReferenceEquals(constant, captureReference));
Debug.Assert(constant.Value.Equals(2));

We then move on to look at the actual ConstantExpression code:

image

Do you see the hook that I am going to plug into?

Coming back to my ReSharper plug, it is pretty easy to see that value is a read only value, but it is not mark as such. This is one of ReSharper’s suggestions that came in 4.0, and made me aware of the reasons to do so. Because it is not mark as such, I have a way in…

All it requires is a bit of LCG (lightweight code generation), such as this:

public static Action<ConstantExpression, object> CreateSetAccessor()
{
    var accessor = new DynamicMethod("SetValue", typeof (void), new Type[]
    {
        typeof (ConstantExpression),
        typeof (object)
    }, true);
    var generator = accessor.GetILGenerator();
    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldarg_1);
    generator.Emit(OpCodes.Stfld,typeof(ConstantExpression).GetField("value",BindingFlags.Instance|BindingFlags.NonPublic));
    generator.Emit(OpCodes.Ret);
    return (Action<ConstantExpression, object>)accessor.CreateDelegate(typeof(Action<ConstantExpression,object>));
}

And now we have a passing test:

var valueSetAccessor = CreateSetAccessor();
var constant = Expression.Constant(5);
var captureReference = constant;

valueSetAccessor(constant, 2);

Debug.Assert(ReferenceEquals(constant, captureReference));
Debug.Assert(constant.Value.Equals(2));

Now, I don’t really recommend doing this. This is unsupported, etc.

But it is a cool trick, and it applies pretty much generically across all of the Expression classes.