Home .NET Generating codewhile the application is running: real examples and techniques

Generating codewhile the application is running: real examples and techniques

by admin

Code generation at runtime is a very powerful and well-studied technique, but many developers are still reluctant to use it.Usually you start learning Expression Trees with some simple example, like creating a predicate (filter) or a mathematical expression.But the .NET developer lives not by Expression Trees alone. More recently, it is possible to generate code using the compiler itself – this is done with the help of Roslyn/CodeAnalisys API libraries, providing, among other things, parsing, traversal and source generation.

This article is based on a report by Raffaele Rialdi (Twitter: @raffaeler ) at the DotNext 2017 Moscow conference. Together with Raphael we will analyze real ways to use codogeneration. In some cases they can dramatically improve the performance of an application, which in turn leads us to a dilemma – if the generated code is so useful and we are going to use it often, then how to debug this code? This is one of the fundamental questions that arise in real projects.

Raphael is a practicing architect, consultant, and speaker with an MVP in the Developer Security category since 2003, who rightnow handles backend enterprise projects, specializing in code generation and cross-platform development for C# and C++.

What is code generation? Suppose you need to demonstrate performance. Ifyou just show a benchmark, it would be a kind of trick, a clever trick. Articles and reports should avoid showing benchmarks, not because it is dangerous for the author, but because a benchmark demonstrates only one scenario, and it is hardly useful for all readers. The reader is forced to try out the proposed technologies and decide whether or not they are suitable for his particular scenarios. So don’t exaggerate the importance of benchmarks. I do them for myself, they show decent results.

We know that a program with reflexion will by definition be slow. It needs to load metadata ECMA-335 and interpret them. They are a very compact set of binary data, they are quite difficult to read. They need to be compact because they don’t need to take up too much memory once they are assembled. Once these artifacts are deployed, performance is poor because we are dealing with a very low-level API. Speaking of which, reflexion can be avoided by loading all these artifacts directly from assemblies. I won’t talk about this in today’s talk, but in case you’re wondering, I’ve used this method before to avoid constant loading and assembly in memory; you can free memory from everything except type information.

When exactly should you generate code? At the point in the life cycle of the application when enough information becomes available to simplify the algorithm. We are talking, for example, about information that can be obtained from the user interface for a filter, which would reduce the number of records retrieved from the database. Or about the information about the types loaded in the plugin. It is extremely undesirable to spend time creating a generic algorithm with reflection that takes into account all possible options. Developers, unfortunately, have a tendency to try to make the solutions they develop as generic as possible, working in all possible and impossible cases. For our programmer’s mind, this is a natural way of thinking. I propose exactly the opposite approach: wait patiently until there is enough information to generate the maximum concise code.

When exactly is it necessary to generate code? For example, when using LINQ predicates. Predicate builders have been available for a long time. Or when using formulas from, say, Excel. Or when loading types from a plugin, or when using Reactive Extensions. How many of you are familiar with Reactive Extensions? It’s a great library that allows you to create data streams and apply expressions that can filter groups and modify that data. I’ll show you many of these examples to demonstrate the power of Reactive Extensions.

Generating codewhile the application is running: real examples and techniques

Let’s start with Expression in C#. Here is a simple example of code that generates a call .. Console.WriteLine. Someone might ask - why use reflexion when you have just pointed out the disadvantages of using reflexion? The answer is not to abandon reflection altogether, but to remove it from the most used parts of the code. You need to find a point in time whereyou can use reflection to extract the necessary amount of data, generate code, and, for example, use delegation within a loop so you don't have to wait for the code to execute.

In the code, I start by getting the exact overload WriteLine , then I create a parameter which then becomes the input message. After that, I create the equivalent of the method Call In the call Expression.Call(null, methodInfo, message) , null denotes a static method ( WriteLine is a static method). In addition, this call also needs arguments with information about the method, and with a message.

After that, the lambda is created. It’s very simple, you just have to specify parameters and body of the lambda. For the already created lambda, a very useful method is called Compile() It’s good because it creates the instruction in memory directly and in a very simple way. There is no source code, there is nothing that needs to be handled in the ways described in "Dragon Book". There is no first step of compilation, i.e., long and complextext analysis. It is not needed because in the case of Expression we already know that it is syntactically correct. This is very important. This is why the Expression tree is so cumbersome; it has an extremely unpleasant strict typing. If you’ve ever tried to form several expressions with each other, you know how nerve-racking it is. But once you have an expression formed, you can really compile it. The compiler simply takes the nodes of the tree (i.e., certain expressions) and creates an appropriate node for the code we want to call. We end up generating a delegate, i.e., the fastest available means to execute the code.

Generating codewhile the application is running: real examples and techniques

I will show you an example in which a predicate will be created. A very simple function that takes an integeras input and returns a boolean value. Let’s look at its code. For the first input value there is a parameter : Expression.Parameter(typeof(int), "x") One of the input arguments to this method is "x" , don’t pay any attention to it, it’s only needed for debugging. The variable left stands for the left part of the expression x > -10 , right – right. From these two variables, a binary comparison expression is created. Finally, the expression Lambda This is preferable to returning a delegate in this case, because it will be possible to make changes to it if necessary. To do this, you can use the Visitor pattern, which will number all the nodes inside the expression, and change it in a very precise way. You don’t have to do any work with text, you go straight to the node you want.

Generating codewhile the application is running: real examples and techniques

Here is an example wheresome call needs to be visited. Suppose there is a predicate extraction from a node where , since the code is written in LINQ. Once you have the Expression you need, you can write a Visitor to it. And this Expression can be found since where is a call to an extension method. The first parameter where is IQueryable<T> , and returns a boolean value. So we know exactly which form we need. But if we need to add something to this Expression, we can do it in the place where the ellipsis is written.

Generating codewhile the application is running: real examples and techniques

So that we don’t get bored, let’s move on to demonstrations. I originally didn’t want to write a parsing tool because it’s boring, such a program is usually slow, and there are libraries that do this task better than self-written code. I wanted something small and easily modifiable. And when writing parsing tools you come to the point where you have to write a grammar and you have to use a lot of libraries. Besides, I wanted to write the tool in such a way that the nodes created after the analysis were similar to what Expressions actually expresses. In the end I came up with a way to represent, for example, the expression x + y (which you see in the code) in the form of text, and then recognize it.

Generating codewhile the application is running: real examples and techniques

That is, I tried to express the parameters manually. I did it for simplicity, and perhaps this can be avoided. At least it is important to specify the types, because in Expression you cannot use the first step of compilation. For example, automatic type conversion or implicit type conversion is not available, the conversion integer in double All this has to be done manually.

If you run the code you see on the screen in the debugger, an Expression will be returned. The lambda is represented in the Visual Studio debugger in a rather strange way, but there’s nothing wrong with that. It looks complicated, but in the end it’s just x + y , you can live with it.

Generating codewhile the application is running: real examples and techniques

Let’s see how we can translate the function I wrote in text SUM() The text visualizer shows us the variable e where the result of the translation is currently located. You can see that I have defined FunctionsHelper with a predefined function, just like Excel does. This kind of application has to predefine some kind of dictionary of functions. It’s simple enough.

Generating codewhile the application is running: real examples and techniques

Let’s try to go a little bit further in the code. There’s a function GetFilter()

Generating codewhile the application is running: real examples and techniques

As you can see, this is a lambda. Usually in such cases it returns Func<int, bool> and nothing else. But the compiler has a special feature that allows, if there are no square brackets in the body of the function, to return Expression<Func<int, bool> > That is, an Expression is automatically created for this representation. This is very convenient because it can still be changed. If you want to remove a number and replace it with something else, you can just write a Visitor for the expression and use it to make all the necessary changes.

Generating codewhile the application is running: real examples and techniques

Let’s look at the second demonstration. In it, we have from the beginning the predicate Expression<Func<int, bool> > predicate

I want to make an injection into it that gives output to the command line. I pass predicates and two lambdas to the injector and specify when I get the value x each time to output {x} => YES or {x} => NO If we look at what the variable looks like injected after starting the application, we see a function with the operator If , it has been changed quite a bit from its original value.

Generating codewhile the application is running: real examples and techniques

So, here we have an integer as input, and we inject ... If , the console outputs YES or NO depending on the value, and finally the value processed by the expression is returned.These kinds of code changes are already in practice, and they are quite powerful.

There’s a problem that you’ve probably already noticed – the renderer I showed you the generated code in so far presents the information in a rather strange form.Programming with expressions has some advantages, but from a developer’s point of view the code gets "messy".

Let’s go back to the demonstration.I’ve already talked about delayed execution: until the enumeration of numbers is complete, the next code will not be executed.If we get to toList , we get both the list and the Console.WriteLine for them, which in this case will execute automatically.

Generating codewhile the application is running: real examples and techniques

Generating codewhile the application is running: real examples and techniques

It all looks good, but I want to try something more complicated.The following example came to me in a dream. I want to create a lambda that, when compiled, converts data in a dictionary (probably JSON) in a certain order. The task is fairly straightforward.

Generating codewhile the application is running: real examples and techniques

If you execute this code with reflexion, the resultwill be what you see on the screen now.

Generating codewhile the application is running: real examples and techniques

Iterates through the mapping properties, searches the dictionary for a match for each property, and copies it. Obviously, this code will be slow. If it’s only executed once, it’s not a problem, but ifit needs to be executed a million times, well, you get the idea. If it’s going to happen in a server application that consumes server resources, someone might not like it.

Let’s try to solve this problem in a different way. Here in the code we create an object `Order, the elements of which will be put in line with the input to the class dictionary.

Generating codewhile the application is running: real examples and techniques

Values are extracted from the dictionary, then reduced to the desired type, copied, and it’s all totally creepy and boring.

But what if I create a lambda that already knows the object Order ?

Generating codewhile the application is running: real examples and techniques

It is important that I specify the type of this object. Notice that I don’t use <Order> That would be great, but what if we don’t know this type? What if. Order is defined in a plugin with delayed loading? Generics may help in some cases, but in this case it would be undesirable because we may need to abstract this information.

So, let’s look at our lambda after compilation.

Generating codewhile the application is running: real examples and techniques

Isn’t she good? The code is nice to read. It was generated using Expressions. Let’s look at how they are written in the ExpressionGeneration

Generating codewhile the application is running: real examples and techniques

We see that the code is similar to what I wrote with reflexion. Defined by Expression.Parameter() , a variable is defined result , a new newEntityType with the help of Activator.CreateInstance , the new instance is assigned to the variable assign Everything is very boring. Then I get the method via type.getMethod() and after that I bypass the properties of entityProps

Generating codewhile the application is running: real examples and techniques

You don’t need to create a loop in this case because we know how many properties there will be. Therefore, exactly the calls we need to retrieve the necessary value for callTryGetValue

Generating codewhile the application is running: real examples and techniques

On the next line, the method Expression.Convert() , a type conversion must be done for it, since the types can be different. Next, to access the property, a call is made to Expression.MakeMemberAccess() After that, a call is made to Expression.IfThen() for the try-catchconstruct. Finally, a block is created, i.e., an opening and closing parenthesis. And as a result, we get a lambda.

Generating codewhile the application is running: real examples and techniques

I wrote the tool ExpressionsSorcerer You can take its code and put it in the directory %USERPROFILE%/Visual Studio 2017/Visualizers , and run the debugging of the code I just reviewed again. This time I will be able to see the lambda through the visualizer, it will be represented as a tree.

Generating codewhile the application is running: real examples and techniques

This kind of operation can be very useful, it makes you wonder what the hell I wrote here. When you select an individual tree node, the properties and their values appear in the window on the right, which is very handy. Open the "Show the decompiled source" tab. This is the code I would have written if I had the information passed to the code generator.

Generating codewhile the application is running: real examples and techniques

But I haven’t even laid a finger on that code. I didn’t even generate C#-code. I wrote Expressions, i.e. only the syntax nodes were in memory, and I needed to decompile. Thanks to Roslyn there’s also color markup, it can be changed if needed. In addition, I added the attribute DebuggableAttribute because I don’t want any optimizations that might happen during compilation. You may ask why I don’t need them. And in answer I have one more surprise for you.

If we compile with debugging (by pressing "F11"), we enter an automatically generated method which we didn’t write with our own hands. Impressive, isn’t it? Here you can see the current values of the variables and check if there are any errors in the Expressions. As you can see, the values are Description was not present in the input argument, so the method TryGetValue was used for a reason.

Generating codewhile the application is running: real examples and techniques

At the end of the function in question we get the variable order with the correct number of values.

Here’s an intermediate summary. Expressions cover almost the whole language, you can use them to generate operators if , throw , catch , you can create complex constructions. But you’ll probably need a special tool to do that. In my tool, the most difficult part to write was the implicit type conversions. If you created a variable double x and try to assign the type integer its value, you will get an exception InvalidCastException The reason is that the implicit conversion is done by the compiler, and we didn’t have one. So we had to do some things that are normally done by the compiler.

Let me demonstrate some more complex expressions. The code on the screen is where a very simple object is created var newObject = ExpressionInterop.BuildNewObject(ctor)

Generating codewhile the application is running: real examples and techniques

If you look at it in the visualization tool, you can see how a new object is created new Order()

Generating codewhile the application is running: real examples and techniques

For reasons already mentioned, I always recommend using the typeof() Next, through the method GetConstructor I get the constructor I want, and then I use the GetMethod – the methods I need. After that, a new object is created, which is passed the information about the constructor : ExpressionInterop.BuildNewObject(ctor) And so on.

I’m not going to elaborate on that. But I would like to show you what an expression looks like when you assign a value to a property…

Here are the compilation artifacts :

Generating codewhile the application is running: real examples and techniques

Generating codewhile the application is running: real examples and techniques

But if we go back to the real Expression, it looks pretty messy. One of the most complicated Expressions I’ve created is used for marshalling. I generated code that allows me to execute asynchronous code for AddAsync

Generating codewhile the application is running: real examples and techniques

even if there is no code in the expression to represent Task<T>

Generating codewhile the application is running: real examples and techniques

The code is pretty messed up, you can’t recompile it because the compiler, Mono.Cecil. , can’t create a perfect decompilation. Perhaps it will be able to do so in the future. In addition, the problem here is that for Task<int> needs to inject an external function. This is necessary because Expressions were created before asynchronous libraries and before changes in compiler to support async/await. That’s why it’s impossible to do the generation with the compiler and use await. The compiler does all the magic, so if you use ILSpy and look at the artifact created with await, you’ll see there a colbec with continuation. The code turns out to be very complicated.

So where were we? We created Expressions to generate certain predicates, functions, fairly complex pieces of code with if-then-else constructs, throw-catch, and more. Now let’s talk about Roslyn.

Generating codewhile the application is running: real examples and techniques

Roslyn, the .NET compiler platform, has been running as the main compiler for C# for several years now. In other words, it rules our world. There used to be little we could do, but Roslyn has opened up the API for us. Now with this compiler’s API we can directly do a bunch of stuff. We have formatting, character information, you can compile things, interpret characters, get into metadata behind the assembler, and more.As for color markup, Roslyn doesn’t directly control it.It doesn’t say "this should be green and that should be blue".There is just a classification of analyzed lexemes, and they can be displayed in different ways.

So we have quite a few tools available, but there is a problem.There is no strict typing in Roslyn.There are syntax nodes, and they are very easy to use because any element is a syntax node. There is no need to spend attention on connecting nodes to each other. But this has a downside. Without the very hard typing that gets on our nerves so much when working with Expressions, we never know for sure if the code we’re writing will work correctly. So with Roslyn, there’s a greater chance of errors occurring than in code written with Expressions.
Still, the advantages of Roslyn are great. It covers the whole language, i.e. you can create any constructions. For example, you can refer to Roslyn if you want to create new types at runtime. Suppose I want to create a DTO (Data Transfer Objects) of a non-existent object at runtime. I don’t want to resort to AutoMapper, because AutoMapper is usually used at runtime. The type created will have to be able to filter events, each of which will be of a different type. If you want to specify Expression, you have to create it and then work with the type that represents that data. And you’ll need a DTO to deserialize them.

The first and easiest way to generate code with Roslyn is the parser, which has an API.

Generating codewhile the application is running: real examples and techniques

It parses the text, creates a syntax tree that you can do all sorts of things with:change the format, make nice indents, transform. Suppose you need to refactor the API, change variable names or replace calls, say, . Console.WriteLine. to Console.Write Instead of creating everything from scratch, you can read the existing code, use it as a template, and replace only what you need. The Visitor template works very well for this purpose. You can visit some of the tokens in the application, and, finding the one you need, replace it. As you can see from the slide, formatting is very easy.

If this functionality is not enough, you can use SyntaxGenerator. This is a powerful high-level API and has a syntax factory underneath it. In it you can declare namespaces, classes, attributes, parameters, in other words, it’s a full-fledged language. And with the command node.AdjustWhitespace() you can make standard spaces between nodes.

Generating codewhile the application is running: real examples and techniques

First, let’s look at some examples of how this tool works. In the first one we use SyntaxFactory from which we get SyntaxTrivia , QualifiedName , CompilationUnit , UsingDirective You might say – this is even worse than Expressions trees. But what you see here is a low-level API. It’s useful to know it, and you can explore it with the SDK for Roslyn. In it, you can see how the syntax tree of the code is created and how the nodes in Roslyn attach to each other. This is very important because if you don’t understand how to represent a method call, what the generics are, the return value or whatever, you can see it all here in the syntax tree visualizer. So, it’s a powerful tool, you can use it to create beautiful diagrams and more.

Going back to our example, I run the debugger in it again. After getting the last syntax node the source code is available. In theory, you could do with just the syntax nodes and get rid of the source code, since you don’t want to have to re-run the syntax analysis of the whole tree when compiling it, of course. Nevertheless, having code can be useful for two reasons. Firstly, it is the only way to specify the encoding. The compiler may make a mistake if it misunderstands the encoding of the source code. Second, having code is very important for debugging. Even in production, the sources of the generated code should be kept for some time. It can serve as a kind of log.

The second example with Roslyn is also very simple.

Generating codewhile the application is running: real examples and techniques

Generating codewhile the application is running: real examples and techniques

We can compare the generated code in the variable text :

Generating codewhile the application is running: real examples and techniques

And the way it looked after formatting ( text2 ):

Generating codewhile the application is running: real examples and techniques

As you can see, there are no more prepositions left to use StringBuilder -om to create code.

The following example.

Generating codewhile the application is running: real examples and techniques

I take a piece of generated code :

Generating codewhile the application is running: real examples and techniques

And I convert it with PostProcess(SyntaxNode root) This is possible because LINQ looks for the nodes I need and replaces them with other nodes. Let’s say I need to replace the command Console.WriteLine. to Console.Write After that you need to find the block in brackets and add the method Console.ReadKey() As a result the converted code will execute Console.Write and right after it Console.ReadKey

Generating codewhile the application is running: real examples and techniques

So you can change the code as needed. It is not difficult and gives a huge amount of possibilities.

And another example.

Generating codewhile the application is running: real examples and techniques

I have created a high-level syntax here, represented by the object CodeGenerationHelper() With SyntaxGenerator you can abstract to create classes, their properties, etc.

Generating codewhile the application is running: real examples and techniques

This example creates a POCO as a DTO, very convenient.

I would like to show you another generator, more advanced.

Generating codewhile the application is running: real examples and techniques

We all love abstractions, don’t we? A syntax generator that can do almost anything is likely to be difficult to use. For simplicity, it can be somewhat restricted to an individual use case. In the code you see two classes. In the first one you just create properties and I will show how exactly it happens in the test. But first I will show you how the generator works.

Generating codewhile the application is running: real examples and techniques

In the generated code, we see that an abstraction has been created that gives us exactly what is expected from, for example, a DTO. A very simple class has been created that only contains properties. And it mimics the one I gave as input. So, it’s a very useful tool.

The second example in my abstraction calls AddImplementINotifyPropertyChanged()

Generating codewhile the application is running: real examples and techniques

Generating codewhile the application is running: real examples and techniques

So if you look in the visualizer at the field result.DiagnosticReport , we will see the full implementation of the INotifyPropertyChanged

Generating codewhile the application is running: real examples and techniques

There is an event announcement, a setter for string _name with a call to OnPropertyChanged() , and the implementation of the method OnPropertyChanged , with [CallerMemberName] – That is, as they teach in the manuals. That’s a pretty good result. I’ll post some of these examples on GitHub so you can work with them directly.

The question arises – how does all this magic happen? I had to write quite a lot of code for this, as you can see – my generator has many classes, they are just wrappers for all possible SyntaxGenerator actions. I won’t go through all of them now, but you can discuss them later if you want. By the way, in the simplest example above, there aren’t many extra classes at all. Let’s take a look at the SimpleClassGenerator

Generating codewhile the application is running: real examples and techniques

The code is generated in the constructor, in the field HashSet<PortableExecutableReference> Reference stores the desired references, e.g. System.Runtime By the way, most of the demonstrated code can run on both .NET Core and the .NET Framework, but I’m a fan of .NET Core, so I usually do my demonstrations based on it.

Generating codewhile the application is running: real examples and techniques

Returning to the class SimpleClassGenerator , there is also a string dictionary IDictionary<string, Properties> Properties , which accumulates information about syntax nodes. In the method GetSource() is called to another method of the same class, BuildClass() which simply adds class declarations to the syntax nodes.

Generating codewhile the application is running: real examples and techniques

Then in this method for each item in the dictionary I specify a lambda-expression, the body of which is a call to the method CreateProperty()

Generating codewhile the application is running: real examples and techniques

There, in turn, a trivial property is created. Despite the fact that the property is trivial, the method turned out to be big. The reason for this is simple. Usually properties have a backfield. To make a property trivial I need to remove some of the code and replace it with a simpler annotation. This is why getting and specifying Accessor takes three lines each.

Let’s spend the last few paragraphs of the article on an obvious question. Since we’re talking about code generation, why didn’t we say anything about IL? You probably imagined that the report would talk about Reflection.Emit. , about poking around in memory directly and that sort of thing. I confess that I adore assembler. I was born with it. I started out programming in x86 assembler. At that stage it was really needed, there was a big difference between working with a variable in memory or with registers.You could not count on a compiler. But things are different now. Now you need a very serious reason to turn to assembler, and not just a desire to "code closer to the hardware". I like it, but that’s another question.

The ability to modify IL code can, indeed, be very useful. Imagine there is a third-party .dll file, and we don’t know what it does. With ILSpy we can open that file and look at the code. But this is where the familiar feeling comes in: the code is in front of our eyes, but it’s still not clear what will happen at runtime. A huge number of classes are available, which don’t say anything at first. To figure it out somehow, you can install the Visual Studio plugin for IL code, which allows you to do runtime reflection and debug the code. But now you have to press "F11" for each launch, and after several hours of such work there is no more patience, and the meaning of the code is still not clear.

There are things that can be automated in this situation. After all, in essence, our work comes down to automating various tasks. Mono.Cecil is a very powerful tool. It allows you to fetch instructions, decompile, filter, view, analyze their contents, consider arguments of instructions. Let’s imagine a scenario where we need to identify all the calls from a certain section of code. There are a lot of similar scenarios, and there are many tools on GitHub that perform similar tasks. So, I’m going to wait for the calls and look for the code that executes them. But instead of hitting "F11" on every call when debugging, I want to instrument this IL code to do the logging.

What exactly would that look like? I have a library sample1.dll , it has the class DataHelper , which emulates a call to the database.

Generating codewhile the application is running: real examples and techniques

Class Employee is a DTO. There is a class Person , class Printer which gives output to the console. And finally, the class Main , which, depending on the argument, executes one of two launching methods.

Generating codewhile the application is running: real examples and techniques

Method Start1 simply outputs the data Person on the screen. Start2 loads the data, creates a lambda-expression that converts the input argument into a string, and, having received a list of strings, displays them with Printer It is worth noting that the for-each construction in the method ToList() triggers a lot of things. There’s a lot going on internally for Linq processes, `Enumerable, and more.

So, let’s try to run this application. I will use AssemblyHooker which acts as a trap for the .dll library. Now you can open VisualStudio Code, and look at the log that was created by the application. This log was created by the code I inserted into sample1.dll.

Generating codewhile the application is running: real examples and techniques

In the abstract I have included all the currently available information. It is not very convenient to read this journal, so let’s try to present this data in a different format. Do you know what is PlantUML ? It’s a magic library that lets you convert that weird dialect we see on the screen into a flow diagram.

Generating codewhile the application is running: real examples and techniques

What we end up with is a flow diagram of what happens during the execution of a program. This is not a static analysis, which is a hell of a thing. This is what we have after the run. We see in the diagram the moment when the name and age values were set, and then, after the method WriteLine , we retrieve these values and convert them to strings. The diagram also shows the actors of each action : Program , Person , Console In my opinion, this is a terrific tool.

Generating codewhile the application is running: real examples and techniques

Now let’s try to run the program through the second method of the class Main in sample1.dll. Here, things will be much more complicated. As you remember, the second method performs many different operations. After performing the same operations as with the first method, we get the diagram PlantUML As you can see, one of the axes in the diagram is marked as Enumerable , i.e. we can even see what’s going on inside the framework. I didn’t write anything to the hard drive, the code insertion happened in memory, at runtime. So you saw that call with the reflexion in the class Main

So, this is, in my opinion, the potential of using IL directly. Generating methods with it, I think, is useless, there are more suitable tools for that. But modifying existing IL code may help in certain situations.

In conclusion, I will say that, in my vision, the future of code generation is likely to involve artificial intelligence. Admittedly, there is something scary about this. I see such a scheme : a kind of contract of very small libraries capable of generating the necessary code, and over them AI-driven algorithms. This could become a very powerful machine. What will emerge from this? I can’t say. I’m still just exploring this field, if you’d like to get into it with me, I’d be only too happy to.

To summarize. Don’t use code generation just anywhere, free from it the most frequently used code sections. There is an important tool I suggest you test. Roslyn Quoter. If you type some expression into it, it will show the calls that Roslyn will need to create that expression. I had the creepy idea of trying to type in Roslyn Quoter. calls received from the same, and it caused the code generation stack to overflow.

A moment of publicity. As you probably know, we do conferences. The nearest .NET conference is. DotNext 2018 Piter. It will be held on April 22-23, 2018 in St. Petersburg. What papers there are – you can see at our YouTube archive At the conference, you’ll be able to interact live with speakers and top .NET experts in special discussion zones after each talk. In short, stop by, we’re waiting for you.

You may also like