开发者

Where is "code as data" in DLR expression?

开发者 https://www.devze.com 2023-02-17 06:29 出处:网络
I have this c# code: Console.Writeline(\"Hello World\"); If I want to do this with DLR expression it looks something like this:

I have this c# code:

Console.Writeline("Hello World");

If I want to do this with DLR expression it looks something like this:

MethodInfo method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) });
Expression callExpression = Expression.Call(null, method, Expression.Constant("Hello   World"));
Action callDelegate = Expression.Lambda<Action>(callExpression).Compile();
callDelegate();

I took this example from the book Pro DLR in .NET 4. And I don't understand why are we doing this extra work? Book says the reason is that once code is represented as objects in memory it is far easier to analyze than IL instruction..

What confuses me the most: If I put DLR expression instead of ConsoleWriteline() method in my code and run my console applicaton I will get the same .exe file (that contains CIL code) and I will get "Hello world" written in the Console as a result of .exe file being executed. So in both cases I get .exe file (cil code) that is executed, and I c开发者_运维知识库an not see where are those objects that represents code as data at the run time and how can I access them?


Basically, what the second code snippet is doing is encapsulating the call as an expression tree. Expression trees are relatively new to .NET (they were necessary to implement Linq interop with data mechanisms other than in-memory objects), and encapsulate program instructions in a mutable, but still executable form.

If you wanted, once you had the expression, you could change the text to be output from "Hello World" to "Hello Dolly" by changing the value of the Constant node referenced by the Call node. You could change the Call node to use a different MethodInfo, for instance a call to Debug.WriteLine() or a custom WriteToLog() method you develop. You can also pass that expression around, save it, serialize it, and call it much further down the line than this simple example. All of these changes and decisions can be made at runtime based on information that is not known at compile-time. A dynamically-constructed expression tree can be created based on data in a file or the database, which is easy to change and doesn't require releasing a new version of the DLL or EXE containing this line.

By contrast, the "static" call to Console.WriteLine() can only be changed at compile-time (notwithstanding the possibility of some VERY messy IL-emitting dynamic code), requiring such a change if the requirements for where that string is written change.


I can not see where are those objects that represents code as data at the run time and how can I access them?

It's theExpression that represents code as data: it represents a call to the Console.WriteLine method with "Hello World" as the only argument.

Here's an example of discovering this fact at run-time:

var method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) });
var callExpression = Expression.Call(null, method, Expression.Constant("Hello   World"));

var expression = Expression.Lambda<Action>(callExpression);  

// Now let's try to inspect 'expression'

var body = expression.Body as MethodCallExpression;

if (body != null)
{
    Console.WriteLine("Expn's body is a method-call expn.");    
    Console.WriteLine("...that calls:" + body.Method.Name);

    var args = body.Arguments;

    if (args.Any())
    {
        Console.WriteLine("The call has arguments.");    

        var firstArg = args.First() as ConstantExpression;

        if (firstArg != null)
        {
            Console.WriteLine("The first argument is a constant expn.");
            Console.WriteLine("...with value " + firstArg.Value);
        }
    }
}


Just as an aside, another way to generate the expression is:

Expression<Action> e=()=>Console.WriteLine("Hello World");

This will save you having to write the boilerplate reflection code.

0

精彩评论

暂无评论...
验证码 换一张
取 消