Ayende @ Rahien

Hi!
My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 10 | Comments: 37

filter by tags archive

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.


Comments

Krzysztof Kozmic

Even if the field was marked readonly, you still could do that (not sure view LCG, but certainly via reflection).

readonly is more like a suggestion, not a hard rule, and CLS does not even require a language to support that IIRC.

Ayende Rahien

Krzysztof,

That is not accurate for LCG

You will get:

Unhandled Exception: System.Security.VerificationException: Operation could dest

abilize the runtime.

at SetValue(MyConstantExpression , Object )

at ConsoleApplication1.Program.Main(String[] args) in c:\temp\ConsoleApplication1\ConsoleApplication1\Program.cs:line 31

Using reflection, it works, but I don't know why

Krzysztof Kozmic

Oh, I see - than my statement about CLS not requiring it probably is wrong.

Peter Morris

I would be far too scared to use code like this :-) You might have a working product released when a .NET framework service pack comes along and plugs the whole. Ouch!

Although I did find the LCG code very interesting. It's something I've been meaning to look into for ages but never had a reason to allocate the time to :-)

liviu

Hi Ayende,

Now .net 4.0 has made the ExpressionVisitor from System.Core public.

That is very nice, say good by to hacks!!!

liviu

And by the way, they listened to your recomandation:

the private members are READONLY now for Expression types....

In .NET 4.0

Comment preview

Comments have been closed on this topic.

FUTURE POSTS

  1. Production postmortem: The case of the memory eater and high load - one day from now
  2. Production postmortem: The case of the lying configuration file - about one day from now
  3. Production postmortem: The industry at large - 3 days from now
  4. The insidious cost of allocations - 4 days from now
  5. Find the bug: The concurrent memory buster - 5 days from now

And 4 more posts are pending...

There are posts all the way to Sep 10, 2015

RECENT SERIES

  1. Find the bug (5):
    20 Apr 2011 - Why do I get a Null Reference Exception?
  2. Production postmortem (10):
    14 Aug 2015 - The case of the man in the middle
  3. What is new in RavenDB 3.5 (7):
    12 Aug 2015 - Monitoring support
  4. Career planning (6):
    24 Jul 2015 - The immortal choices aren't
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats