{"id":334,"date":"2023-10-20T13:35:11","date_gmt":"2023-10-20T05:35:11","guid":{"rendered":"https:\/\/miie.net\/?p=334"},"modified":"2023-10-20T13:35:11","modified_gmt":"2023-10-20T05:35:11","slug":"pro-c10-chapter-18-understanding-cil-and-the-role-of-dynamic-assemblies","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=334","title":{"rendered":"Pro C#10 CHAPTER 18 Understanding CIL and the Role of Dynamic Assemblies"},"content":{"rendered":"<p>CHAPTER 18<\/p>\n<p>Understanding CIL and the Role of Dynamic Assemblies<\/p>\n<p>When you are building a full-scale .NET application, you will most certainly use C# (or a similar managed language such as Visual Basic), given its inherent productivity and ease of use. However, as you learned in the beginning of this book, the role of a managed compiler is to translate *.cs code files into terms of CIL code, type metadata, and an assembly manifest. As it turns out, CIL is a full-fledged .NET programming language, with its own syntax, semantics, and compiler (ilasm.exe).<br \/>\nIn this chapter, you will be given a tour of .NET\u2019s mother tongue. Here, you will understand the distinction between a CIL directive, CIL attribute, and CIL opcode. You will then learn about the role of round-trip engineering of a .NET Core assembly and various CIL programming tools. The remainder of the chapter will then walk you through the basics of defining namespaces, types, and members using the grammar of CIL. The chapter will wrap up with an examination of the role of the System.Reflection.Emit namespace and explain how it is possible to construct an assembly (with CIL instructions) dynamically at runtime.<br \/>\nOf course, few programmers will ever need to work with raw CIL code on a day-to-day basis. Therefore, I will start this chapter by examining a few reasons why getting to know the syntax and semantics of this low- level .NET language might be worth your while.<\/p>\n<p>Motivations for Learning the Grammar of CIL<br \/>\nWhen you build a .NET assembly using your managed language of choice (C#, VB, F#, etc.), the associated compiler translates your source code into terms of CIL. Like any programming language, CIL provides numerous structural and implementation-centric tokens. Given that CIL is just another .NET programming language, it should come as no surprise that it is possible to build your .NET assemblies directly using CIL and the CIL compiler (ilasm.exe).<\/p>\n<p>\u00a9 Andrew Troelsen, Phil Japikse 2022<br \/>\nA. Troelsen and P. Japikse, Pro C# 10 with .NET 6, <a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_18\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_18\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_18\">https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_18<\/a><\/a><\/a><\/p>\n<p>713<\/p>\n<p>\u25a0 Note  As covered in Chapter 1, neither ildasm.exe nor ilasm.exe ships with the .NET Runtime.<br \/>\nThere are two options for getting these tools. The first is to compile the .NET Runtime from the source located at <a href=\"https:\/\/github.com\/dotnet\/runtime\"><a href=\"https:\/\/github.com\/dotnet\/runtime\"><a href=\"https:\/\/github.com\/dotnet\/runtime\">https:\/\/github.com\/dotnet\/runtime<\/a><\/a><\/a>. The second, and easier, method is to pull down the desired version from www.nuget.org. The URL for ILDasm on NuGet is <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft\"><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft\"><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft\">https:\/\/www.nuget.org\/packages\/Microsoft<\/a><\/a><\/a>. NETCore.ILDAsm\/, and for ILAsm.exe it is <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.NETCore\"><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.NETCore\"><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.NETCore\">https:\/\/www.nuget.org\/packages\/Microsoft.NETCore<\/a><\/a><\/a>. ILAsm\/. Make sure to select the correct version (for this book you need version 6.0.0 or greater). Add the ILDasm and ILAsm NuGet packages to your project with the following commands: dotnet add package Microsoft. NETCore.ILDAsm --version 6.0.0dotnet add package Microsoft.NETCore.ILAsm --version 6.0.0<br \/>\nThis does not actually add ILDasm.exe or ILAsm.exe into your project but places them in your package folder (on Windows): %userprofile%.nuget\\packages\\microsoft.netcore.ilasm\\6.0.0\\runtimes\\ native\\%userprofile%.nuget\\packages\\microsoft.netcore.ildasm\\6.0.0\\runtimes\\native\\<br \/>\nI have also included the 6.0.0 version of both programs in this book\u2019s GitHub repo.<\/p>\n<p>Now while it is true that few (if any!) programmers would choose to build an entire .NET application directly with CIL, CIL is still an extremely interesting intellectual pursuit. Simply put, the more you understand the grammar of CIL, the better able you are to move into the realm of advanced .NET development. By way of some concrete examples, individuals who possess an understanding of CIL are capable of the following:<br \/>\n\u2022Disassembling an existing .NET assembly, editing the CIL code, and recompiling the updated code base into a modified .NET binary. For example, there are some scenarios where you might need to modify the CIL to interoperate with some advanced COM features.<br \/>\n\u2022Building dynamic assemblies using the System.Reflection.Emit namespace. This API allows you to generate an in-memory .NET assembly, which can optionally be persisted to disk. This is a useful technique for the tool builders of the world who need to generate assemblies on the fly.<br \/>\n\u2022Understanding aspects of the Common Type System (CTS) that are not supported by higher-level managed languages but do exist at the level of CIL. To be sure, CIL is the only .NET language that allows you to access every aspect of the CTS. For example, using raw CIL, you can define global-level members and fields (which are not permissible in C#).<br \/>\nAgain, to be perfectly clear, if you choose not to concern yourself with the details of CIL code, you are still able to gain mastery of C# and the .NET base class libraries. In many ways, knowledge of CIL is analogous to a C (and C++) programmer\u2019s understanding of assembly language. Those who know the ins and outs of the low-level \u201cgoo\u201d can create rather advanced solutions for the task at hand and gain a<br \/>\ndeeper understanding of the underlying programming (and runtime) environment. So, if you are up for the challenge, let\u2019s begin to examine the details of CIL.<\/p>\n<p>\u25a0 Note  This chapter is not intended to be a comprehensive treatment of the syntax and semantics of CIL. We are really just skimming the surface. If you want (or need) to get deeper into CIL, consult the documentation.<\/p>\n<p>Examining CIL Directives, Attributes, and Opcodes<br \/>\nWhen you begin to investigate low-level languages such as CIL, you are guaranteed to find new (and often intimidating sounding) names for familiar concepts. For example, the previous chapters showed CIL examples that contained the following set of items:<\/p>\n<p>{new, public, this, base, get, set, explicit, unsafe, enum, operator, partial}<\/p>\n<p>After reading the chapters leading up to this in this text, you understand them to be keywords of the C# language. However, if you look more closely at the members of this set, you might be able to see that while each item is indeed a C# keyword, it has radically different semantics. For example, the enum keyword defines a System.Enum-derived type, while the this and base keywords allow you to reference the current object or the object\u2019s parent class, respectively. The unsafe keyword is used to establish a block of code that cannot be directly monitored by the CLR, while the operator keyword allows you to build a hidden (specially named) method that will be called when you apply a specific C# operator (such as the plus sign).<br \/>\nIn stark contrast to a higher-level language such as C#, CIL does not just simply define a general set of keywords per se. Rather, the token set understood by the CIL compiler is subdivided into the following three broad categories based on semantics:<br \/>\n\u2022CIL directives<br \/>\n\u2022CIL attributes<br \/>\n\u2022CIL operation codes (opcodes)<br \/>\nEach category of CIL token is expressed using a particular syntax, and the tokens are combined to build a valid .NET assembly.<\/p>\n<p>The Role of CIL Directives<br \/>\nFirst up, there is a set of well-known CIL tokens that are used to describe the overall structure of a .NET assembly. These tokens are called directives. CIL directives are used to inform the CIL compiler how to define the namespaces(s), type(s), and member(s) that will populate an assembly.<br \/>\nDirectives are represented syntactically using a single dot (.) prefix (e.g., .namespace, .class,<br \/>\n.publickeytoken, .method, .assembly, etc.). Thus, if your *.il file (the conventional extension for a file containing CIL code) has a single .namespace directive and three .class directives, the CIL compiler will generate an assembly that defines a single .NET Core namespace containing three .NET class types.<\/p>\n<p>The Role of CIL Attributes<br \/>\nIn many cases, CIL directives in and of themselves are not descriptive enough to fully express the definition of a given .NET type or type member. Given this fact, many CIL directives can be further specified with various CIL attributes to qualify how a directive should be processed. For example, the .class directive can be adorned with the public attribute (to establish the type visibility), the extends attribute (to explicitly specify the type\u2019s base class), and the implements attribute (to list the set of interfaces supported by<br \/>\nthe type).<\/p>\n<p>\u25a0 Note  Don\u2019t confuse a .NET attribute (see Chapter 17) with that of a CIL attribute, which are two very different concepts.<\/p>\n<p>The Role of CIL Opcodes<br \/>\nOnce a .NET assembly, namespace, and type set have been defined in terms of CIL using various directives and related attributes, the final remaining task is to provide the type\u2019s implementation logic. This is a job for operation codes, or simply opcodes. In the tradition of other low-level languages, many CIL opcodes tend to be cryptic and completely unpronounceable by us mere humans. For example, if you need to load a string variable into memory, you do not use a friendly opcode named LoadString but rather ldstr.<br \/>\nNow, to be fair, some CIL opcodes do map quite naturally to their C# counterparts (e.g., box, unbox, throw, and sizeof). As you will see, the opcodes of CIL are always used within the scope of a member\u2019s<br \/>\nimplementation, and unlike CIL directives, they are never written with a dot prefix.<\/p>\n<p>The CIL Opcode\/CIL Mnemonic Distinction<br \/>\nAs just explained, opcodes such as ldstr are used to implement the members of a given type. However, tokens such as ldstr are CIL mnemonics for the actual binary CIL opcodes. To clarify the distinction, assume you have authored the following method in C# in a .NET Console Application named FirstSamples:<\/p>\n<p>int Add(int x, int y)<br \/>\n{<br \/>\nreturn x + y;<br \/>\n}<\/p>\n<p>The act of adding two numbers is expressed in terms of the CIL opcode 0X58. In a similar vein, subtracting two numbers is expressed using the opcode 0X59, and the act of allocating a new object on the managed heap is achieved using the 0X73 opcode. Given this reality, understand that the \u201cCIL code\u201d processed by a JIT compiler is nothing more than blobs of binary data.<br \/>\nThankfully, for each binary opcode of CIL, there is a corresponding mnemonic. For example, the add mnemonic can be used rather than 0X58, sub rather than 0X59, and newobj rather than 0X73. Given this opcode\/mnemonic distinction, realize that CIL decompilers such as ildasm.exe translate an assembly\u2019s binary opcodes into their corresponding CIL mnemonics. For example, here would be the CIL presented by ildasm.exe for the previous C# Add() method (your exact output may differ based on your version of<br \/>\n.NET Core):<\/p>\n<p>.method \/<em>06000002<\/em>\/ assembly hidebysig static int32 '&lt;<Main>$&gt;g Add|0_0'(int32 x, int32 y) cil managed<br \/>\n\/\/ SIG: 00 02 08 08 08<br \/>\n{<br \/>\n\/\/ Method begins at RVA 0x2060<br \/>\n\/\/ Code size    9 (0x9)<br \/>\n.maxstack 2<br \/>\n.locals \/<em>11000001<\/em>\/ init (int32 V_0)<br \/>\nIL_0000:    \/<em> 00   |   <\/em>\/ nop<br \/>\nIL_0001:    \/<em> 02   |   <\/em>\/ ldarg.0<br \/>\nIL_0002:    \/<em> 03   |   <\/em>\/ ldarg.1<br \/>\nIL_0003:    \/<em> 58   |   <\/em>\/ add<br \/>\nIL_0004:    \/<em> 0A   |   <\/em>\/ stloc.0<br \/>\nIL_0005:    \/<em> 2B   | 00    <\/em>\/ br.s IL_0007<br \/>\nIL_0007:    \/<em> 06   |   <\/em>\/ ldloc.0<br \/>\nIL_0008:    \/<em> 2A   |   <\/em>\/ ret<br \/>\n} \/\/ end of method '<Program>$'::'&lt;<Main>$&gt;g Add|0_0'<\/p>\n<p>Unless you are building some extremely low-level .NET software (such as a custom managed compiler), you will never need to concern yourself with the literal numeric binary opcodes of CIL. For all practical purposes, when .NET programmers speak about \u201cCIL opcodes,\u201d they are referring to the set of friendly string token mnemonics (as I have done within this text and will do for the remainder of this chapter) rather than the underlying numerical values.<\/p>\n<p>Pushing and Popping: The Stack-Based Nature of CIL<br \/>\nHigher-level .NET languages (such as C#) attempt to hide low-level CIL grunge from view as much as possible. One aspect of .NET development that is particularly well hidden is that CIL is a stack-based programming language. Recall from the examination of the collection namespaces (see Chapter 10) that the Stack<T> class can be used to push a value onto a stack as well as pop the topmost value off the stack for use. Of course, CIL developers do not use an object of type Stack<T> to load and unload the values to be evaluated; however, the same pushing and popping mindset still applies.<br \/>\nFormally speaking, the entity used to hold a set of values to be evaluated is termed the virtual execution stack. As you will see, CIL provides several opcodes that are used to push a value onto the stack; this process is termed loading. As well, CIL defines additional opcodes that transfer the topmost value on the stack into memory (such as a local variable) using a process termed storing.<br \/>\nIn the world of CIL, it is impossible to access a point of data directly, including locally defined variables, incoming method arguments, or field data of a type. Rather, you are required to explicitly load the item onto the stack, only to then pop it off for later use (keep this point in mind, as it will help explain why a given block of CIL code can look a bit redundant).<\/p>\n<p>\u25a0 Note  Recall that CIL is not directly executed but compiled on demand. During the compilation of CIL code, many of these implementation redundancies are optimized away. furthermore, if you enable the code<br \/>\noptimization option for your current project (using the build tab of the Visual studio project properties window or adding <Optimize>true<\/Optimize> into the main property group of the project file), the compiler will also remove various CIL redundancies.<\/p>\n<p>To understand how CIL leverages a stack-based processing model, consider a simple C# method, PrintMessage(), which takes no arguments and returns void. Within the implementation of this method, you will simply print the value of a local string variable to the standard output stream, like so:<\/p>\n<p>void PrintMessage()<br \/>\n{<br \/>\nstring myMessage = &quot;Hello.&quot;; Console.WriteLine(myMessage);<br \/>\n}<\/p>\n<p>If you were to examine how the C# compiler translates this method in terms of CIL, you would first find that the PrintMessage() method defines a storage slot for a local variable using the .locals directive. The local string is then loaded and stored in this local variable using the ldstr (load string) and stloc.0 opcodes (which can be read as \u201cstore the current value in a local variable at storage slot zero\u201d).<\/p>\n<p>The value (again, at index 0) is then loaded into memory using the ldloc.0 (\u201cload the local argument at index 0\u201d) opcode for use by the System.Console.WriteLine() method invocation (specified using the call opcode). Finally, the function returns via the ret opcode. Here is the (annotated) CIL code for the PrintMessage() method (note that I have removed the nop opcodes from this listing, for brevity):<\/p>\n<p>.method assembly hidebysig static void<br \/>\n'&lt;<Main>$&gt;g PrintMessage|0_1'() cil managed<br \/>\n{<br \/>\n\/\/ Method begins at RVA 0x2064<br \/>\n\/\/ Code size    13 (0xd)<br \/>\n.maxstack 1<br \/>\n\/\/ Define a local string variable (at index 0).<br \/>\n.locals init (string V_0)<br \/>\n\/\/ Load a string onto the stack with the value &quot;Hello.&quot;<br \/>\nIL_0000: ldstr  &quot;Hello.&quot;<br \/>\n\/\/ Store string value on the stack in the local variable.<br \/>\nIL_0005: stloc.0<br \/>\n\/\/ Load the value at index 0.<br \/>\nIL_0006: ldloc.0<br \/>\n\/\/ Call method with current value.<br \/>\nIL_0007: call   void System.Console::WriteLine(string) IL_000c: ret<br \/>\n} \/\/ end of method '<Program>$'::'&lt;<Main>$&gt;g PrintMessage|0_1'<\/p>\n<p>\u25a0 Note  As you can see, CIL supports code comments using the double-slash syntax (as well as the \/<em>...<\/em>\/<br \/>\nsyntax, for that matter). As in C#, code comments are completely ignored by the CIL compiler.<\/p>\n<p>Now that you have the basics of CIL directives, attributes, and opcodes, let\u2019s see a practical use of CIL programming, beginning with the topic of round-trip engineering.<\/p>\n<p>Understanding Round-Trip Engineering<br \/>\nYou are aware of how to use ildasm.exe to view the CIL code generated by the C# compiler (see Chapter 1). Once you have the CIL code at your disposal, you are free to edit and recompile the code base using the CIL compiler, ilasm.exe.<br \/>\nFormally speaking, this technique is termed round-trip engineering, and it can be useful under select circumstances, such as the following:<br \/>\n\u2022You need to modify an assembly for which you no longer have the source code.<br \/>\n\u2022You are working with a less-than-perfect .NET language compiler that has emitted ineffective (or flat-out incorrect) CIL code, and you want to modify the code base.<br \/>\n\u2022You are constructing a COM interoperability library and want to account for some COM IDL attributes that have been lost during the conversion process (such as the COM [helpstring] attribute).<\/p>\n<p>To illustrate the process of round-tripping, begin by creating a new C# .NET Core Console application named RoundTrip. Update the Program.cs file to the following:<\/p>\n<p>\/\/ A simple C# console app. Console.WriteLine(&quot;Hello CIL code!&quot;); Console.ReadLine();<\/p>\n<p>Compile your program using the .NET Core CLI.dotnet build<\/p>\n<p>\u25a0 Note Recall from Chapter 1 that all .NET Core assemblies (class libraries or console apps) by default are compiled to assemblies that have a *.dll extension and are executed using dotnet.exe. New in .NET Core<br \/>\n3.0 (and newer), the dotnet.exe file is copied into the output directory and renamed to match the assembly name. so, while it looks like your project was compiled to RoundTrip.exe, it was compiled to RoundTrip. dll with dotnet.exe copied to RoundTrip.exe along with the required command-line arguments needed to execute Roundtrip.dll. If you publish as a single file (covered in Chapter 16), then RoundTrip.exe contains even more than just your code.<\/p>\n<p>Next execute ildasm.exe against RoundTrip.dll using the following command (executed from the solution folder level):<\/p>\n<p>ildasm \/all \/METADATA \/out=.\\RoundTrip\\RoundTrip.il .\\RoundTrip\\bin\\Debug\\net6.0\\ RoundTrip.dll<\/p>\n<p>The previous command output just about everything contained in the assembly, including the file headers, hex commands as comments, all metadata, and much more. If you want a more concise file to work with when examining IL code, you can drop the \/all and \/METADATA options. However, for these examples, you need all of the extra information.<\/p>\n<p>\u25a0Note  ildasm.exe will also generate a *.res file when dumping the contents of an assembly to file. These resource files can be ignored (and deleted) throughout this chapter, as you will not be using them. This file contains some low-level CLR security information (among other things).<\/p>\n<p>Now you can view RoundTrip.il using Visual Studio, Visual Studio Code, or your text editor of choice.<br \/>\nFirst, notice that the *.il file opens by declaring each externally referenced assembly that the current assembly is compiled against. If your class library used additional types within other referenced assemblies (beyond System.Runtime and System.Console), you would find additional .assembly extern directives.<\/p>\n<p>.assembly extern System.Runtime<br \/>\n{<br \/>\n.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )<br \/>\n.ver 6:0:0:0<br \/>\n}<br \/>\n.assembly extern System.Console<br \/>\n{<br \/>\n.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )<br \/>\n.ver 6:0:0:0<br \/>\n}<\/p>\n<p>Next, you find the formal definition of your RoundTrip.dll assembly, described using various CIL directives (such as .module, .imagebase, etc.).<\/p>\n<p>.assembly RoundTrip<br \/>\n{<br \/>\n...<br \/>\n.hash algorithm 0x00008004<br \/>\n.ver 1:0:0:0<br \/>\n}<br \/>\n.module RoundTrip.dll<br \/>\n.imagebase 0x00400000<br \/>\n.file alignment 0x00000200<br \/>\n.stackreserve 0x00100000<br \/>\n.subsystem 0x0003   \/\/ WINDOWS_CUI<br \/>\n.corflags 0x00000001    \/\/ ILONLY<\/p>\n<p>After documenting the externally referenced assemblies and defining the current assembly, you find a definition of the Program type, created from the top-level statements. Note that the .class directive has<br \/>\nvarious attributes (many of which are optional) such as extends, shown here, which marks the base class of<br \/>\nthe type:<\/p>\n<p>.class private abstract auto ansi sealed beforefieldinit '<Program>$' extends [System.Runtime]System.Object<br \/>\n{ ... }<\/p>\n<p>The bulk of the CIL code represents the implementation of the class\u2019s default constructor and the autogenerated Main() method, both of which are defined (in part) with the .method directive. Once the members have been defined using the correct directives and attributes, they are implemented using various opcodes.<\/p>\n<p>.method private hidebysig static void '<Main>$'(string[] args) cil managed<br \/>\n{<br \/>\n.entrypoint<br \/>\n\/\/ Code size    18 (0x12)<br \/>\n.maxstack 8<br \/>\nIL_0000: ldstr  &quot;Hello CIL code!&quot;<br \/>\nIL_0005: call   void [System.Console]System.Console::WriteLine(string) IL_000a: nop<br \/>\nIL_000b: call   string [System.Console]System.Console::ReadLine() IL_0010: pop<br \/>\nIL_0011: ret<br \/>\n} \/\/ end of method '<Program>$'::'<Main>$'<\/p>\n<p>It is critical to understand that when interacting with .NET Core types (such as System.Console) in CIL, you will always need to use the type\u2019s fully qualified name. Furthermore, the type\u2019s fully qualified name must always be prefixed with the friendly name of the defining assembly (in square brackets), as in the following two lines from the generated Main() method:<\/p>\n<p>IL_0005: call   void [System.Console]System.Console::WriteLine(string)<br \/>\nIL_000b: call string [System.Console]System.Console::ReadLine()<\/p>\n<p>The Role of CIL Code Labels<br \/>\nOne thing you certainly have noticed is that each line of implementation code is prefixed with a token of the form IL_XXX: (e.g., IL_0000:, IL_0001:, etc.). These tokens are called code labels and may be named in any manner you choose (provided they are not duplicated within the same member scope). When you dump an assembly to file using ildasm.exe, it will automatically generate code labels that follow an IL_XXX: naming convention. However, you may change them to reflect a more descriptive marker. Here is an example:<br \/>\n.method private hidebysig static void Main(string[] args) cil managed<br \/>\n{<br \/>\n.entrypoint<br \/>\n.maxstack 8<br \/>\nLoad_String: ldstr &quot;Hello CIL code!&quot;<br \/>\nPrintToConsole: call void [System.Console]System.Console::WriteLine(string) Nothing_2: nop<br \/>\nWaitFor_KeyPress: call string [System.Console]System.Console::ReadLine() RemoveValueFromStack: pop<br \/>\nLeave_Function: ret<br \/>\n}<\/p>\n<p>The truth of the matter is that most code labels are completely optional. The only time code labels are truly mandatory is when you are authoring CIL code that makes use of various branching or looping<br \/>\nconstructs, as you specify where to direct the flow of logic via these code labels. For the current example, you can remove these autogenerated labels altogether with no ill effect, like so:<br \/>\n.method private hidebysig static void Main(string[] args) cil managed<br \/>\n{<br \/>\n.entrypoint<br \/>\n.maxstack 8<br \/>\nldstr &quot;Hello CIL code!&quot;<br \/>\ncall void [System.Console]System.Console::WriteLine(string) nop<br \/>\ncall string [System.Console]System.Console::ReadLine() pop<br \/>\nret<br \/>\n}<\/p>\n<p>Interacting with CIL: Modifying an <em>.il File<br \/>\nNow that you have a better understanding of how a basic CIL file is composed, let\u2019s complete the round- tripping experiment. The goal here is quite simple: change the message that is output to the console. You can do more, such as add assembly references or create new classes and methods, but we will keep it simple.<br \/>\nTo make the change, you need to alter the current implementation of the top-level statements, created as the <Main>$ method. Locate this method within the <\/em>.il file and change the message to \u201cHello from altered CIL Code!\u201d<br \/>\nIn effect, you have just updated the CIL code to correspond to the following C# class definition:<\/p>\n<p>static void Main(string[] args)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Hello from altered CIL code!&quot;); Console.ReadLine();<br \/>\n}<\/p>\n<p>There are two ways to create compile .NET assemblies using an <em>.il file. Using the IL project type provides more flexibility but is a bit more involved. The second simply uses ILASM.EXE to create a <\/em>.dll file from the IL file. We will explore using ILASM.EXE first.<\/p>\n<p>Compiling CIL Code with ILASM.EXE<br \/>\nStart by creating a new directory on your machine (in the samples on GitHub, I named the new directory RoundTrip2). In this directory, copy in the updated RoundTrip.il file. Also copy the RoundTrip. runtimeconfig.json file from the RoundTrip\\bin\\Debug.net6.0 folder. This file is needed for executables created using ILASM.EXE to configure the target framework moniker and the target framework. For reference, the contents of the file are listed here:<\/p>\n<p>{<br \/>\n&quot;runtimeOptions&quot;: { &quot;tfm&quot;: &quot;net6.0&quot;, &quot;framework&quot;: {<br \/>\n&quot;name&quot;: &quot;Microsoft.NETCore.App&quot;, &quot;version&quot;: &quot;6.0.0-preview.3.21201.4&quot;<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>Finally, compile the assembly with the following command from the RoundTrip2 directory (update the path to ILASM.EXE as necessary):<\/p>\n<p>....\\ilasm \/DLL RoundTrip.il \/X64<\/p>\n<p>To execute the program, use the CLI, like this:<\/p>\n<p>dotnet RoundTrip.dll<\/p>\n<p>Sure enough, you will see the updated message displaying in the console window.<\/p>\n<p>Compiling CIL Code with Microsoft.NET.Sdk.il Projects<br \/>\nAs you just saw, compiling IL with ILASM.EXE is a bit limited. A much more powerful way is to create a project that uses the Microsoft.NET.Sdk.IL project type. Unfortunately, at the time of this writing, this project type is not included in the standard project templates, so manual intervention is required. Begin by creating a new directory named RoundTrip3 and copying the modified RoundTrip.il file into the new directory.<\/p>\n<p>\u25a0 Note At the time of this writing, Visual studio does directly support the *.ilproj project type. While there are some extensions in the marketplace, I can\u2019t recommend for or against using them. Visual studio Code supports all of the code in this section.<\/p>\n<p>In this directory, create a global.json file. The global.json file applies to the current directory and all subdirectories below the file. It is used to define which SDK version you will use when running .NET Core CLI commands. Update the files to the following:<\/p>\n<p>{<br \/>\n&quot;msbuild-sdks&quot;: { &quot;Microsoft.NET.Sdk.IL&quot;: &quot;6.0.0&quot;<br \/>\n}<br \/>\n}<\/p>\n<p>The next step is to create the project file. Create a file named RoundTrip.ilproj and update it to the following:<\/p>\n<p><Project Sdk=\"Microsoft.NET.Sdk.IL\"><br \/>\n<PropertyGroup><br \/>\n<OutputType>Exe<\/OutputType><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<MicrosoftNetCoreIlasmVersion>6.0.0<\/MicrosoftNetCoreIlasmVersion><br \/>\n<ProduceReferenceAssembly>false<\/ProduceReferenceAssembly><br \/>\n<\/PropertyGroup><br \/>\n<\/Project><\/p>\n<p>Finally, copy in your updated RoundTrip.il file into the directory. Compile the assembly using the<br \/>\n.NET Core CLI:<\/p>\n<p>dotnet build<\/p>\n<p>You will find the resulting files in the usual bin\\debug\\net6.0 folder. At this point, you can run your new application by executing RoundTrip.exe, just as if you built it using a standard C# console application template.<br \/>\nIn addition to a better resulting experience, the IL project can take advantage of producing single-file assemblies, as was covered in Chapter 16. Update the project file to the following:<\/p>\n<p><Project Sdk=\"Microsoft.NET.Sdk.IL\"><br \/>\n<PropertyGroup><br \/>\n<OutputType>Exe<\/OutputType><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<MicrosoftNetCoreIlasmVersion>6.0.0-preview.3.21201.4<\/MicrosoftNetCoreIlasmVersion><br \/>\n<ProduceReferenceAssembly>false<\/ProduceReferenceAssembly><br \/>\n<PublishSingleFile>true<\/PublishSingleFile><br \/>\n<SelfContained>true<\/SelfContained><br \/>\n<RuntimeIdentifier>win-x64<\/RuntimeIdentifier><br \/>\n<PublishReadyToRun>true<\/PublishReadyToRun><br \/>\n<PublishTrimmerd>true<\/PublishTrimmed><br \/>\n<\/PropertyGroup><br \/>\n<\/Project><\/p>\n<p>Now you can publish as a stand-alone file just like a C# project. Use the publish command to see this in action:<\/p>\n<p>dotnet publish -r win-x64 -p:PublishSingleFile=true -c release -o singlefile --self- contained true<\/p>\n<p>While the output of this simple example is not all that spectacular, it does illustrate one practical use of programming in CIL: round-tripping.<\/p>\n<p>Understanding CIL Directives and Attributes<br \/>\nNow that you have seen how to convert .NET Core assemblies into IL and compile IL into assemblies, you can get down to the business of checking out the syntax and semantics of CIL itself. The next sections will walk you through the process of authoring a custom namespace containing a set of types. However, to keep things simple, these types will not contain any implementation logic for their members (yet). After you understand how to create empty types, you can then turn your attention to the process of defining \u201creal\u201d members using CIL opcodes.<\/p>\n<p>Specifying Externally Referenced Assemblies in CIL<br \/>\nIn a new directory named CILTypes, copy the global.json file from the previous example. Create a new project file named CILTypes.ilproj, and update it to the following:<\/p>\n<p><Project Sdk=\"Microsoft.NET.Sdk.IL\"><br \/>\n<PropertyGroup><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<MicrosoftNetCoreIlasmVersion>6.0.0<\/MicrosoftNetCoreIlasmVersion><br \/>\n<ProduceReferenceAssembly>false<\/ProduceReferenceAssembly><br \/>\n<\/PropertyGroup><br \/>\n<\/Project><\/p>\n<p>Next, create a new file named CILTypes.il using your editor of choice. The first task a CIL project will require is to list the set of external assemblies used by the current assembly. For this example, you will only use types found within System.Runtime.dll. To do so, the .assembly directive will be qualified using the external attribute. When you are referencing a strongly named assembly, such as System.Runtime.dll, you will want to specify the .publickeytoken and .ver directives as well, like so:<\/p>\n<p>.assembly extern System.Runtime<br \/>\n{<br \/>\n.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )<br \/>\n.ver 6:0:0:0<br \/>\n}<br \/>\n.assembly extern System.Runtime.Extensions<br \/>\n{<br \/>\n.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )<br \/>\n.ver 6:0:0:0<br \/>\n}<br \/>\n.assembly extern mscorlib<\/p>\n<p>{<br \/>\n.publickeytoken = (B7 7A 5C 56 19 34 E0 89)<br \/>\n.ver 6:0:0:0<br \/>\n}<\/p>\n<p>Defining the Current Assembly in CIL<br \/>\nThe next order of business is to define the assembly you are interested in building using the .assembly directive. At the simplest level, an assembly can be defined by specifying the friendly name of the binary, like so:<\/p>\n<p>\/\/ Our assembly.<br \/>\n.assembly CILTypes { }<\/p>\n<p>While this indeed defines a new .NET Core assembly, you will typically place additional directives within the scope of the assembly declaration. For this example, update your assembly definition to include a version number of 1.0.0.0 using the .ver directive (note that each numerical identifier is separated by colons, not the C#-centric dot notation), as follows:<\/p>\n<p>\/\/ Our assembly.<br \/>\n.assembly CILTypes<br \/>\n{<br \/>\n.ver 1:0:0:0<br \/>\n}<\/p>\n<p>Given that the CILTypes assembly is a single-file assembly, you will finish up the assembly definition using the following single .module directive, which marks the official name of your .NET binary, CILTypes.dll:<\/p>\n<p>.assembly CILTypes<br \/>\n{<br \/>\n.ver 1:0:0:0<br \/>\n}<br \/>\n\/\/ The module of our single-file assembly.<br \/>\n.module CILTypes.dll<\/p>\n<p>In addition to .assembly and .module are CIL directives that further qualify the overall structure of the<br \/>\n.NET binary you are composing. Table 18-1 lists a few of the more common assembly-level directives.<\/p>\n<p>Table 18-1. Additional Assembly-Centric Directives<\/p>\n<p>Directive   Meaning in Life<br \/>\n.mresources If your assembly uses internal resources (such as bitmaps or string tables), this directive is used to identify the name of the file that contains the resources to be embedded.<br \/>\n.subsystem  This CIL directive is used to establish the preferred UI that the assembly wants to execute within. For example, a value of 2 signifies that the assembly should run within a GUI application, whereas a value of 3 denotes a console executable.<\/p>\n<p>Defining Namespaces in CIL<br \/>\nNow that you have defined the look and feel of your assembly (and the required external references), you can create a .NET Core namespace (MyNamespace) using the .namespace directive, like so:<\/p>\n<p>\/\/ Our assembly has a single namespace.<br \/>\n.namespace MyNamespace {}<\/p>\n<p>Like C#, CIL namespace definitions can be nested within further namespaces. There is no need to define a root namespace here; however, for the sake of argument, assume you want to create the following root namespace named MyCompany:<\/p>\n<p>.namespace MyCompany<br \/>\n{<br \/>\n.namespace MyNamespace {}<br \/>\n}<\/p>\n<p>Like C#, CIL allows you to define a nested namespace as follows:<\/p>\n<p>\/\/ Defining a nested namespace.<br \/>\n.namespace MyCompany.MyNamespace {}<\/p>\n<p>Defining Class Types in CIL<br \/>\nEmpty namespaces are not remarkably interesting, so let\u2019s now check out the process of defining a class type using CIL. Not surprisingly, the .class directive is used to define a new class. However, this simple directive can be adorned with numerous additional attributes, to further qualify the nature of the type. To illustrate, add a public class to your namespace named MyBaseClass. As in C#, if you do not specify an explicit base class, your type will automatically be derived from System.Object.<\/p>\n<p>.namespace MyNamespace<br \/>\n{<br \/>\n\/\/ System.Object base class assumed.<br \/>\n.class public MyBaseClass {}<br \/>\n}<\/p>\n<p>When you are building a class type that derives from any class other than System.Object, you use the extends attribute. Whenever you need to reference a type defined within the same assembly, CIL demands that you also use the fully qualified name (however, if the base type is within the same assembly, you can omit the assembly\u2019s friendly name prefix). Therefore, the following attempt to extend MyBaseClass results in a compiler error:<\/p>\n<p>\/\/ This will not compile!<br \/>\n.namespace MyNamespace<br \/>\n{<br \/>\n.class public MyBaseClass {}<\/p>\n<p>.class public MyDerivedClass<br \/>\nextends MyBaseClass {}<br \/>\n}<\/p>\n<p>To correctly define the parent class of MyDerivedClass, you must specify the full name of MyBaseClass<br \/>\nas follows:<\/p>\n<p>\/\/ Better!<br \/>\n.namespace MyNamespace<br \/>\n{<br \/>\n.class public MyBaseClass {}<\/p>\n<p>.class public MyDerivedClass<br \/>\nextends MyNamespace.MyBaseClass {}<br \/>\n}<\/p>\n<p>In addition to the public and extends attributes, a CIL class definition may take numerous additional qualifiers that control the type\u2019s visibility, field layout, and so on. Table 18-2 illustrates some (but not all) of the attributes that may be used in conjunction with the .class directive.<\/p>\n<p>Table 18-2. Various Attributes Used in Conjunction with the .class Directive<\/p>\n<p>Attributes  Meaning in Life<br \/>\npublic, private, nested assembly, nested famandassem, nested family, nested famorassem, nested public, nested private   CIL defines various attributes that are used to specify the visibility of a given type. As you can see, raw CIL offers numerous<br \/>\npossibilities other than those offered by C#. Refer to ECMA 335 for details if you are interested.<br \/>\nabstract, sealed    These two attributes may be tacked onto a .class directive to define an abstract class or sealed class, respectively.<br \/>\nauto, sequential, explicit  These attributes are used to instruct the CLR how to lay out field data in memory. For class types, the default layout flag (auto) is appropriate. Changing this default can be helpful if you need to use P\/Invoke to call into unmanaged C code.<br \/>\nextends, implements These attributes allow you to define the base class of a type (via<br \/>\nextends) or implement an interface on a type (via implements).<\/p>\n<p>Defining and Implementing Interfaces in CIL<br \/>\nAs odd as it might seem, interface types are defined in CIL using the .class directive. However, when the .class directive is adorned with the interface attribute, the type is realized as a CTS interface type.<br \/>\nOnce an interface has been defined, it may be bound to a class or structure type using the CIL implements<br \/>\nattribute, like so:<\/p>\n<p>.namespace MyNamespace<br \/>\n{<br \/>\n\/\/ An interface definition.<br \/>\n.class public interface IMyInterface {}<\/p>\n<p>\/\/ A simple base class.<br \/>\n.class public MyBaseClass {}<\/p>\n<p>\/\/ MyDerivedClass now implements IMyInterface,<\/p>\n<p>\/\/ and extends MyBaseClass.<br \/>\n.class public MyDerivedClass extends MyNamespace.MyBaseClass<br \/>\nimplements MyNamespace.IMyInterface {}<br \/>\n}<\/p>\n<p>\u25a0 Note  The extends clause must precede the implements clause. As well, the implements clause can incorporate a comma-separated list of interfaces.<\/p>\n<p>As you recall from Chapter 10, interfaces can function as the base interface to other interface types to build interface hierarchies. However, contrary to what you might be thinking, the extends attribute cannot be used to derive interface A from interface B. The extends attribute is used only to qualify a type\u2019s base class. When you want to extend an interface, you will use the implements attribute yet again. Here is an example:<\/p>\n<p>\/\/ Extending interfaces in terms of CIL.<br \/>\n.class public interface IMyInterface {}<\/p>\n<p>.class public interface IMyOtherInterface implements MyNamespace.IMyInterface {}<\/p>\n<p>Defining Structures in CIL<br \/>\nThe .class directive can be used to define a CTS structure if the type extends System.ValueType. As well, the .class directive must be qualified with the sealed attribute (given that structures can never be a base structure to other value types). If you attempt to do otherwise, ilasm.exe will issue a compiler error.<\/p>\n<p>\/\/ A structure definition is always sealed.<br \/>\n.class public sealed MyStruct<br \/>\nextends [System.Runtime]System.ValueType{}<\/p>\n<p>Do be aware that CIL provides a shorthand notation to define a structure type. If you use the value attribute, the new type will derive the type from [System.Runtime]System.ValueType automatically. Therefore, you could define MyStruct as follows:<\/p>\n<p>\/\/ Shorthand notation for declaring a structure.<br \/>\n.class public sealed value MyStruct{}<\/p>\n<p>Defining Enums in CIL<br \/>\n.NET Core enumerations (as you recall) derive from System.Enum, which is a System.ValueType (and therefore must also be sealed). When you want to define an enum in terms of CIL, simply extend [System. Runtime]System.Enum, like so:<\/p>\n<p>\/\/ An enum.<br \/>\n.class public sealed MyEnum<br \/>\nextends [System.Runtime]System.Enum{}<\/p>\n<p>Like a structure definition, enumerations can be defined with a shorthand notation using the enum<br \/>\nattribute. Here is an example:<\/p>\n<p>\/\/ Enum shorthand.<br \/>\n.class public sealed enum MyEnum{}<\/p>\n<p>You will see how to specify the name-value pairs of an enumeration in just a moment.<\/p>\n<p>Defining Generics in CIL<br \/>\nGeneric types also have a specific representation in the syntax of CIL. Recall from Chapter 10 that a given generic type or generic member may have one or more type parameters. For example, the List<T> type has a single<br \/>\ntype parameter, while Dictionary&lt;TKey, TValue&gt; has two. In terms of CIL, the number of type parameters is specified using a backward-leaning single tick (`), followed by a numerical value representing the number of type parameters. Like C#, the actual value of the type parameters is encased within angled brackets.<\/p>\n<p>\u25a0 Note  on Us keyboards, you can usually find the ` character on the key above the Tab key (and to the left of the 1 key).<\/p>\n<p>For example, assume you want to create a List<T> variable, where T is of type System.Int32. In C#, you would type the following:<br \/>\nvoid SomeMethod()<br \/>\n{<br \/>\nList<int> myInts = new List<int>();<br \/>\n}<br \/>\nIn CIL, you would author the following (which could appear in any CIL method scope):<\/p>\n<p>\/\/ In C#: List<int> myInts = new List<int>();<br \/>\nnewobj instance void class [System.Collections] System.Collections.Generic.List<code>1<int32>::.ctor() Notice that this generic class is defined as List<\/code>1<int32>, as List<T> has a single type parameter.<br \/>\nHowever, if you needed to define a Dictionary&lt;string, int&gt; type, you would do so as follows:<\/p>\n<p>\/\/ In C#: Dictionary&lt;string, int&gt; d = new Dictionary&lt;string, int&gt;();<\/p>\n<p>newobj instance void class [System.Collections] System.Collections.Generic.Dictionary`2&lt;string,int32&gt;<br \/>\n::.ctor()<\/p>\n<p>As another example, if you have a generic type that uses another generic type as a type parameter, you would author CIL code such as the following:<\/p>\n<p>\/\/ In C#: List&lt;List<int>&gt; myInts = new List&lt;List<int>&gt;();<br \/>\nnewobj instance void class [mscorlib]<br \/>\nSystem.Collections.Generic.List<code>1&lt;class [System.Collections] System.Collections.Generic.List<\/code>1<int32>&gt;<br \/>\n::.ctor()<\/p>\n<p>Compiling the CILTypes.il File<br \/>\nEven though you have not yet added any members or implementation code to the types you have defined, you are able to compile this *.il file into a .NET Core DLL assembly (which you must do, as you have not specified a Main() method). Open a command prompt and enter the following command:<\/p>\n<p>dotnet build<\/p>\n<p>After you have done so, you can now open your compiled assembly into ildasm.exe to verify the creation of each type. To understand how to populate a type with content, you first need to examine the fundamental data types of CIL.<\/p>\n<p>.NET Base Class Library, C#, and CIL Data Type Mappings<br \/>\nTable 18-3 illustrates how a .NET base class type maps to the corresponding C# keyword and how each C# keyword maps into raw CIL. As well, Table 18-3 documents the shorthand constant notations used for each CIL type. As you will see in just a moment, these constants are often referenced by numerous CIL opcodes.<\/p>\n<p>Table 18-3. Mapping .NET Base Class Types to C# Keywords and C# Keywords to CIL<\/p>\n<p>.NET Core Base Class Type   C# Keyword  CIL Representation  CIL Constant Notation<br \/>\nSystem.SByte    sbyte   int8    I1<br \/>\nSystem.Byte byte    unsigned int8   U1<br \/>\nSystem.Int16    short   int16   I2<br \/>\nSystem.UInt16   ushort  unsigned int16  U2<br \/>\nSystem.Int32    int int32   I4<br \/>\nSystem.UInt32   uint    unsigned int32  U4<br \/>\nSystem.Int64    long    int64   I8<br \/>\nSystem.UInt64   ulong   unsigned int64  U8<br \/>\nSystem.Char char    char    CHAR<br \/>\nSystem.Single   float   float32 R4<br \/>\nSystem.Double   double  float64 R8<br \/>\nSystem.Boolean  bool    bool    BOOLEAN<br \/>\nSystem.String   string  string  N\/A<br \/>\nSystem.Object   object  object  N\/A<br \/>\nSystem.Void void    void    VOID<\/p>\n<p>\u25a0 Note  The System.IntPtr and System.UIntPtr types map to native int and native unsigned int<br \/>\n(many CoM interoperability and p\/Invoke scenarios use these extensively).<\/p>\n<p>Defining Type Members in CIL<br \/>\nAs you are already aware, .NET types may support various members. Enumerations have some set of name- value pairs. Structures and classes may have constructors, fields, methods, properties, static members, and so on. Over the course of this book\u2019s first 18 chapters, you have already seen partial CIL definitions for the items previously mentioned, but nevertheless, here is a quick recap of how various members map to CIL primitives.<\/p>\n<p>Defining Field Data in CIL<br \/>\nEnumerations, structures, and classes can all support field data. In each case, the .field directive will be used. For example, let\u2019s breathe some life into the skeleton MyEnum enumeration and define the following three name-value pairs (note the values are specified within parentheses):<\/p>\n<p>.class public sealed enum MyEnum<br \/>\n{<br \/>\n.field public static literal valuetype MyNamespace.MyEnum A = int32(0)<br \/>\n.field public static literal valuetype MyNamespace.MyEnum B = int32(1)<br \/>\n.field public static literal valuetype MyNamespace.MyEnum C = int32(2)<br \/>\n}<\/p>\n<p>Fields that reside within the scope of a .NET Core System.Enum-derived type are qualified using the static and literal attributes. As you would guess, these attributes set up the field data to be a fixed value accessible from the type itself (e.g., MyEnum.A).<\/p>\n<p>\u25a0 Note  The values assigned to an enum value may also be in hexadecimal with a 0x prefix.<\/p>\n<p>Of course, when you want to define a point of field data within a class or structure, you are not limited to a point of public static literal data. For example, you could update MyBaseClass to support two points of private, instance-level field data, set to default values.<\/p>\n<p>.class public MyBaseClass<br \/>\n{<br \/>\n.field private string stringField = &quot;hello!&quot;<br \/>\n.field private int32 intField = int32(42)<br \/>\n}<\/p>\n<p>As in C#, class field data will automatically be initialized to an appropriate default value. If you want to allow the object user to supply custom values at the time of creation for each of these points of private field data, you (of course) need to create custom constructors.<\/p>\n<p>Defining Type Constructors in CIL<br \/>\nThe CTS supports both instance-level and class-level (static) constructors. In terms of CIL, instance-level constructors are represented using the .ctor token, while a static-level constructor is expressed via .cctor (class constructor). Both CIL tokens must be qualified using the rtspecialname (return type special name) and specialname attributes. Simply put, these attributes are used to identify a specific CIL token that can be treated in unique ways by a given .NET language. For example, in C#, constructors do not define a return type; however, in terms of CIL, the return value of a constructor is indeed void.<\/p>\n<p>.class public MyBaseClass<br \/>\n{<br \/>\n.field private string stringField<br \/>\n.field private int32 intField<\/p>\n<p>.method public hidebysig specialname rtspecialname instance void .ctor(string s, int32 i) cil managed<br \/>\n{<br \/>\n\/\/ TODO: Add implementation code...<br \/>\n}<br \/>\n}<\/p>\n<p>Note that the .ctor directive has been qualified with the instance attribute (as it is not a static constructor). The cil managed attributes denote that the scope of this method contains CIL code, rather than unmanaged code, which may be used during platform invocation requests.<\/p>\n<p>Defining Properties in CIL<br \/>\nProperties and methods also have specific CIL representations. By way of an example, if MyBaseClass were updated to support a public property named TheString, you would author the following CIL (note again the use of the specialname attribute):<\/p>\n<p>.class public MyBaseClass<br \/>\n{<br \/>\n...<br \/>\n.method public hidebysig specialname<br \/>\ninstance string get_TheString() cil managed<br \/>\n{<br \/>\n\/\/ TODO: Add implementation code...<br \/>\n}<\/p>\n<p>.method public hidebysig specialname<br \/>\ninstance void set_TheString(string 'value') cil managed<br \/>\n{<br \/>\n\/\/ TODO: Add implementation code...<br \/>\n}<\/p>\n<p>.property instance string TheString()<br \/>\n{<br \/>\n.get instance string MyNamespace.MyBaseClass::get_TheString()<br \/>\n.set instance void<\/p>\n<p>MyNamespace.MyBaseClass::set_TheString(string)<br \/>\n}<br \/>\n}<\/p>\n<p>In terms of CIL, a property maps to a pair of methods that take get<em> and set<\/em> prefixes. The .property directive makes use of the related .get and .set directives to map property syntax to the correct \u201cspecially named\u201d methods.<\/p>\n<p>\u25a0 Note  Notice that the incoming parameter to the set method of a property is placed in single quotation marks, which represents the name of the token to use on the right side of the assignment operator within the method scope.<\/p>\n<p>Defining Member Parameters<br \/>\nIn a nutshell, specifying arguments in CIL is (more or less) identical to doing so in C#. For example, each argument is defined by specifying its data type, followed by the parameter name. Furthermore, like C#, CIL provides a way to define input, output, and pass-by-reference parameters. As well, CIL allows you to define a parameter array argument (aka the C# params keyword), as well as optional parameters.<br \/>\nTo illustrate the process of defining parameters in raw CIL, assume you want to build a method that takes an int32 (by value), an int32 (by reference), an [mscorlib]System.Collection.ArrayList, and a single output parameter (of type int32). In terms of C#, this method would look something like the following:<\/p>\n<p>public static void MyMethod(int inputInt,<br \/>\nref int refInt, ArrayList ar, out int outputInt)<br \/>\n{<br \/>\noutputInt = 0; \/\/ Just to satisfy the C# compiler...<br \/>\n}<\/p>\n<p>If you were to map this method into CIL terms, you would find that C# reference parameters are marked with an ampersand (&amp;) suffixed to the parameter\u2019s underlying data type (int32&amp;).<br \/>\nOutput parameters also use the &amp; suffix, but they are further qualified using the CIL [out] token. Also notice that if the parameter is a reference type (in this case, the [mscorlib]System.Collections.ArrayList type), the class token is prefixed to the data type (not to be confused with the .class directive!).<\/p>\n<p>.method public hidebysig static void MyMethod(int32 inputInt, int32&amp; refInt,<br \/>\nclass [System.Runtime.Extensions]System.Collections.ArrayList ar, [out] int32&amp; outputInt) cil managed<br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>Examining CIL Opcodes<br \/>\nThe final aspect of CIL code you will examine in this chapter has to do with the role of various operational codes (opcodes). Recall that an opcode is simply a CIL token used to build the implementation logic for a given member. The complete set of CIL opcodes (which is large) can be grouped into the following broad categories:<br \/>\n\u2022   Opcodes that control program flow<br \/>\n\u2022   Opcodes that evaluate expressions<br \/>\n\u2022   Opcodes that access values in memory (via parameters, local variables, etc.)<br \/>\nTo provide some insight to the world of member implementation via CIL, Table 18-4 defines some of the more useful opcodes that are related to member implementation logic, grouped by related functionality.<\/p>\n<p>Table 18-4. Various Implementation-Specific CIL Opcodes<\/p>\n<p>Opcodes Meaning in Life<br \/>\nadd, sub, mul, div, rem These CIL opcodes allow you to add, subtract, multiply, and divide two values (rem returns the remainder of a division operation).<br \/>\nand, or, not, xor   These CIL opcodes allow you to perform bit-wise operations on two values.<br \/>\nceq, cgt, clt   These CIL opcodes allow you to compare two values on the stack in various manners. Here are some examples:<br \/>\nceq: Compare for equality cgt: Compare for greater than clt: Compare for less than<br \/>\nbox, unbox  These CIL opcodes are used to convert between reference types and value types.<br \/>\nRet This CIL opcode is used to exit a method and return a value to the caller (if necessary).<br \/>\nbeq, bgt, ble, blt, switch  These CIL opcodes (in addition to many other related opcodes) are used to control branching logic within a method. Here are some examples:<br \/>\nbeq: Break to code label if equal<br \/>\nbgt: Break to code label if greater than<br \/>\nble: Break to code label if less than or equal to<br \/>\nblt: Break to code label if less than<br \/>\nAll the branch-centric opcodes require that you specify a CIL code label to jump to if the result of the test is true.<br \/>\nCall    This CIL opcode is used to call a member on a given type.<br \/>\nnewarr, newobj  These CIL opcodes allow you to allocate a new array or new object type into memory (respectively).<\/p>\n<p>The next broad category of CIL opcodes (a subset of which is shown in Table 18-5) is used to load (push) arguments onto the virtual execution stack. Note how these load-specific opcodes take a ld (load) prefix.<\/p>\n<p>Table 18-5. The Primary Stack-Centric Opcodes of CIL<\/p>\n<p>Opcode  Meaning in Life<br \/>\nldarg (with numerous variations)    Loads a method\u2019s argument onto the stack. In addition to the general ldarg (which works in conjunction with a given index that identifies the argument), there are numerous other variations. For example, ldarg opcodes that have a numerical suffix (ldarg.0) hard-code which argument to load. As well, variations of the ldarg opcode allow you to hard-code the data type using the CIL constant notation shown in Table 18-4 (ldarg_I4, for an int32), as well as the data type and value (ldarg_I4_5, to load an int32 with the value of 5).<br \/>\nldc (with numerous variations)  Loads a constant value onto the stack.<br \/>\nldfld (with numerous variations)    Loads the value of an instance-level field onto the stack.<br \/>\nldloc (with numerous variations)    Loads the value of a local variable onto the stack.<br \/>\nLdobj   Obtains all the values gathered by a heap-based object and places them on the stack.<br \/>\nLdstr   Loads a string value onto the stack.<\/p>\n<p>In addition to the set of load-specific opcodes, CIL provides numerous opcodes that explicitly pop the topmost value off the stack. As shown over the first few examples in this chapter, popping a value off the stack typically involves storing the value into temporary local storage for further use (such as a parameter for an upcoming method invocation). Given this, note how many opcodes that pop the current value off the virtual execution stack take an st (store) prefix. Table 18-6 hits the highlights.<\/p>\n<p>Table 18-6. Various Pop-Centric Opcodes<\/p>\n<p>Opcode  Meaning in Life<br \/>\nPop Removes the value currently on top of the evaluation stack but does not bother to store the value<br \/>\nStarg   Stores the value on top of the stack into the method argument at a specified index<br \/>\nstloc (with numerous variations)    Pops the current value from the top of the evaluation stack and stores it in a local variable list at a specified index<br \/>\nStobj   Copies a value of a specified type from the evaluation stack into a supplied memory address<br \/>\nStsfld  Replaces the value of a static field with a value from the evaluation stack<\/p>\n<p>Do be aware that various CIL opcodes will implicitly pop values off the stack to perform the task at hand. For example, if you are attempting to subtract two numbers using the sub opcode, it should be clear that sub will have to pop off the next two available values before it can perform the calculation. Once the calculation is complete, the result of the value (surprise, surprise) is pushed onto the stack once again.<\/p>\n<p>The .maxstack Directive<br \/>\nWhen you write method implementations using raw CIL, you need to be mindful of a special directive named .maxstack. As its name suggests, .maxstack establishes the maximum number of variables that may be pushed onto the stack at any given time during the execution of the method. The good news is that the<br \/>\n.maxstack directive has a default value (8), which should be safe for a vast majority of methods you might be authoring. However, if you want to be explicit, you can manually calculate the number of local variables on the stack and define this value explicitly, like so:<\/p>\n<p>.method public hidebysig instance void Speak() cil managed<br \/>\n{<br \/>\n\/\/ During the scope of this method, exactly<br \/>\n\/\/ 1 value (the string literal) is on the stack.<br \/>\n.maxstack 1<br \/>\nldstr &quot;Hello there...&quot;<br \/>\ncall void [mscorlib]System.Console::WriteLine(string) ret<br \/>\n}<\/p>\n<p>Declaring Local Variables in CIL<br \/>\nLet\u2019s first check out how to declare a local variable. Assume you want to build a method in CIL named MyLocalVariables() that takes no arguments and returns void. Within the method, you want to define three local variables of types System.String, System.Int32, and System.Object. In C#, this member would appear as follows (recall that locally scoped variables do not receive a default value and should be set to an initial state before further use):<\/p>\n<p>public static void MyLocalVariables()<br \/>\n{<br \/>\nstring myStr = &quot;CIL code is fun!&quot;; int myInt = 33;<br \/>\nobject myObj = new object();<br \/>\n}<\/p>\n<p>If you were to construct MyLocalVariables() directly in CIL, you could author the following:<\/p>\n<p>.method public hidebysig static void MyLocalVariables() cil managed<br \/>\n{<br \/>\n.maxstack 8<br \/>\n\/\/ Define three local variables.<br \/>\n.locals init (string myStr, int32 myInt, object myObj)<br \/>\n\/\/ Load a string onto the virtual execution stack.<br \/>\nldstr &quot;CIL code is fun!&quot;<\/p>\n<p>\/\/ Pop off current value and store in local variable [0].<br \/>\nstloc.0<\/p>\n<p>\/\/ Load a constant of type &quot;i4&quot;<br \/>\n\/\/ (shorthand for int32) set to the value 33.<br \/>\nldc.i4.s 33<br \/>\n\/\/ Pop off current value and store in local variable [1].<br \/>\nstloc.1<\/p>\n<p>\/\/ Create a new object and place on stack.<br \/>\nnewobj instance void [mscorlib]System.Object::.ctor()<br \/>\n\/\/ Pop off current value and store in local variable [2].<br \/>\nstloc.2 ret<br \/>\n}<\/p>\n<p>The first step taken to allocate local variables in raw CIL is to use the .locals directive, which is paired with the init attribute. Each variable is identified by its data type and an optional variable name. After the local variables have been defined, you load a value onto the stack (using the various load-centric opcodes) and store the value within the local variable (using the various storage-centric opcodes).<\/p>\n<p>Mapping Parameters to Local Variables in CIL<br \/>\nYou have already seen how to declare local variables in raw CIL using the .locals init directive; however, you have yet to see exactly how to map incoming parameters to local methods. Consider the following static C# method:<\/p>\n<p>public static int Add(int a, int b)<br \/>\n{<br \/>\nreturn a + b;<br \/>\n}<\/p>\n<p>This innocent-looking method has a lot to say in terms of CIL. First, the incoming arguments (a and b) must be pushed onto the virtual execution stack using the ldarg (load argument) opcode. Next, the add opcode will be used to pop the next two values off the stack and find the summation and store the value on the stack yet again. Finally, this sum is popped off the stack and returned to the caller via the ret opcode. If you were to disassemble this C# method using ildasm.exe, you would find numerous additional tokens injected by the build process, but the crux of the CIL code is quite simple.<\/p>\n<p>.method public hidebysig static int32 Add(int32 a, int32 b) cil managed<br \/>\n{<br \/>\n.maxstack 2<br \/>\nldarg.0 \/\/ Load &quot;a&quot; onto the stack. ldarg.1 \/\/ Load &quot;b&quot; onto the stack. add  \/\/ Add both values.<br \/>\nret<br \/>\n}<\/p>\n<p>The Hidden this Reference<br \/>\nNotice that the two incoming arguments (a and b) are referenced within the CIL code using their indexed position (index 0 and index 1), given that the virtual execution stack begins indexing at position 0.<br \/>\nOne thing to be mindful of when you are examining or authoring CIL code is that every nonstatic method that takes incoming arguments automatically receives an implicit additional parameter, which is a reference to the current object (like the C# this keyword). Given this, if the Add() method were defined as nonstatic, like so:<\/p>\n<p>\/\/ No longer static!<br \/>\npublic int Add(int a, int b)<br \/>\n{<br \/>\nreturn a + b;<br \/>\n}<\/p>\n<p>Then the incoming a and b arguments are loaded using ldarg.1 and ldarg.2 (rather than the expected ldarg.0 and ldarg.1 opcodes). Again, the reason is that slot 0 contains the implicit this reference. Consider the following pseudocode:<\/p>\n<p>\/\/ This is JUST pseudocode!<br \/>\n.method public hidebysig static int32 AddTwoIntParams( MyClass_HiddenThisPointer this, int32 a, int32 b) cil managed<br \/>\n{<br \/>\nldarg.0 \/\/ Load MyClass_HiddenThisPointer onto the stack. ldarg.1 \/\/ Load &quot;a&quot; onto the stack.<br \/>\nldarg.2 \/\/ Load &quot;b&quot; onto the stack.<br \/>\n...<br \/>\n}<\/p>\n<p>Representing Iteration Constructs in CIL<br \/>\nIteration constructs in the C# programming language are represented using the for, foreach, while, and do<br \/>\nkeywords, each of which has a specific representation in CIL. Consider the following classic for loop:<\/p>\n<p>public static void CountToTen()<br \/>\n{<br \/>\nfor(int i = 0; i &lt; 10; i++)<br \/>\n{<br \/>\n}<br \/>\n}<\/p>\n<p>Now, as you may recall, the br opcodes (br, blt, etc.) are used to control a break in flow when some condition has been met. In this example, you have set up a condition in which the for loop should break out of its cycle when the local variable i is equal to or greater than the value of 10. With each pass, the value of 1 is added to i, at which point the test condition is yet again evaluated.<br \/>\nAlso recall that when you use any of the CIL branching opcodes, you will need to define a specific code label (or two) that marks the location to jump to when the condition is indeed true. Given these points, ponder the following (edited) CIL code generated via ildasm.exe (including the autogenerated code labels):<\/p>\n<p>.method public hidebysig static void CountToTen() cil managed<br \/>\n{<br \/>\n.maxstack 2<\/p>\n<p>.locals init (int32 V_0, bool V_1)<br \/>\nIL_0000: ldc.i4.0   \/\/ Load this value onto the stack. IL_0001: stloc.0 \/\/ Store this value at index &quot;0&quot;. IL_0002: br.s IL_0007 \/\/ Jump to IL_0008.<br \/>\nIL_0003: ldloc.0    \/\/ Load value of variable at index 0. IL_0004: ldc.i4.1 \/\/ Load the value &quot;1&quot; on the stack. IL_0005: add    \/\/ Add current value on the stack at index 0. IL_0006: stloc.0<br \/>\nIL_0007: ldloc.0    \/\/ Load value at index &quot;0&quot;.<br \/>\nIL_0008: ldc.i4.s 10 \/\/ Load value of &quot;10&quot; onto the stack. IL_0009: clt \/\/ check less than value on the stack IL_000a: stloc.1   \/\/ Store result at index &quot;1&quot;<br \/>\nIL_000b: ldloc.1   \/\/ Load value at index &quot;1&quot;<br \/>\nIL_000c: brtrue.s IL_0003 \/\/ if true jump back to IL_0003 IL_000d: ret<br \/>\n}<\/p>\n<p>In a nutshell, this CIL code begins by defining the local int32 and loading it onto the stack. At this point, you jump back and forth between code labels IL_0008 and IL_0004, each time bumping the value of i by 1 and testing to see whether i is still less than the value 10. If so, you exit the method.<\/p>\n<p>The Final Word on CIL<br \/>\nNow that you see the process for creating an executable from an *.IL file, you are probably thinking \u201cthat is an awful lot of work\u201d and then wondering \u201cwhat\u2019s the benefit?\u201d For the vast majority, you will never create a .NET Core executable from IL. However, being able to understand IL can be helpful if you are trying to dig into an assembly that you do not have the source code for.<br \/>\nThere are also commercial projects that can take a .NET assembly and reverse engineer it into source code. If you have ever used one of these tools, now you know how they work!<\/p>\n<p>Understanding Dynamic Assemblies<br \/>\nTo be sure, the process of building a complex .NET application in CIL would be quite the labor of love. On the one hand, CIL is an extremely expressive programming language that allows you to interact with all the programming constructs allowed by the CTS. On the other hand, authoring raw CIL is tedious, error- prone, and painful. While it is true that knowledge is power, you might indeed wonder just how important it is to commit the laws of CIL syntax to memory. The answer is \u201cit depends.\u201d To be sure, most of your .NET programming endeavors will not require you to view, edit, or author CIL code. However, with the CIL<br \/>\nprimer behind you, you are now ready to investigate the world of dynamic assemblies (as opposed to static assemblies) and the role of the System.Reflection.Emit namespace.<br \/>\nThe first question you may have is \u201cWhat exactly is the difference between static and dynamic assemblies?\u201d By definition, static assemblies are .NET binaries loaded directly from disk storage, meaning they are located somewhere in a physical file (or possibly a set of files in the case of a multifile assembly) at the time the CLR requests them. As you might guess, every time you compile your C# source code, you end up with a static assembly.<br \/>\nA dynamic assembly, on the other hand, is created in memory, on the fly, using the types provided by the System.Reflection.Emit namespace. The System.Reflection.Emit namespace makes it possible to create an assembly and its modules, type definitions, and CIL implementation logic at runtime. After<br \/>\nyou have done so, you are then free to save your in-memory binary to disk. This, of course, results in a new<\/p>\n<p>static assembly. To be sure, the process of building a dynamic assembly using the System.Reflection.Emit<br \/>\nnamespace does require some level of understanding regarding the nature of CIL opcodes.<br \/>\nAlthough creating dynamic assemblies is an advanced (and uncommon) programming task, they can be useful under various circumstances. Here is an example:<br \/>\n\u2022   You are building a .NET programming tool that needs to generate assemblies on demand based on user input.<br \/>\n\u2022   You are building a program that needs to generate proxies to remote types on the fly, based on the obtained metadata.<br \/>\n\u2022   You want to load a static assembly and dynamically insert new types into the binary image.<br \/>\nLet\u2019s check out the types within System.Reflection.Emit.<\/p>\n<p>Exploring the System.Reflection.Emit Namespace<br \/>\nCreating a dynamic assembly requires you to have some familiarity with CIL opcodes, but the types of the System.Reflection.Emit namespace hide the complexity of CIL as much as possible. For example,<br \/>\nrather than specifying the necessary CIL directives and attributes to define a class type, you can simply use the TypeBuilder class. Likewise, if you want to define a new instance-level constructor, you have no need to emit the specialname, rtspecialname, or .ctor token; rather, you can use the ConstructorBuilder.<br \/>\nTable 18-7 documents the key members of the System.Reflection.Emit namespace.<\/p>\n<p>Table 18-7. Select Members of the System.Reflection.Emit Namespace<\/p>\n<p>Members Meaning in Life<\/p>\n<p>AssemblyBuilder Used to create an assembly (<em>.dll or <\/em>.exe) at runtime. <em>.exes must<br \/>\ncall the ModuleBuilder.SetEntryPoint() method to set the method that is the entry point to the module. If no entry point is specified, a<br \/>\n<\/em>.dll will be generated.<br \/>\nModuleBuilder   Used to define the set of modules within the current assembly.<br \/>\nEnumBuilder Used to create a .NET enumeration type.<br \/>\nTypeBuilder May be used to create classes, interfaces, structures, and delegates within a module at runtime.<\/p>\n<p>MethodBuilder LocalBuilder PropertyBuilder FieldBuilder ConstructorBuilder CustomAttributeBuilder ParameterBuilder EventBuilder<\/p>\n<p>Used to create type members (such as methods, local variables, properties, constructors, and attributes) at runtime.<\/p>\n<p>ILGenerator Emits CIL opcodes into a given type member.<br \/>\nOpCodes Provides numerous fields that map to CIL opcodes. This type is used in conjunction with the various members of System.Reflection. Emit.ILGenerator.<\/p>\n<p>In general, the types of the System.Reflection.Emit namespace allow you to represent raw CIL tokens programmatically during the construction of your dynamic assembly. You will see many of these members in the example that follows; however, the ILGenerator type is worth checking out straightaway.<\/p>\n<p>The Role of the System.Reflection.Emit.ILGenerator<br \/>\nAs its name implies, the ILGenerator type\u2019s role is to inject CIL opcodes into a given type member. However, you cannot directly create ILGenerator objects, as this type has no public constructors; rather, you receive an ILGenerator type by calling specific methods of the builder-centric types (such as the MethodBuilder and ConstructorBuilder types). Here is an example:<\/p>\n<p>\/\/ Obtain an ILGenerator from a ConstructorBuilder<br \/>\n\/\/ object named &quot;myCtorBuilder&quot;.<br \/>\nConstructorBuilder myCtorBuilder = helloWorldClass.DefineConstructor( MethodAttributes.Public,<br \/>\nCallingConventions.Standard, constructorArgs);<br \/>\nILGenerator myCILGen = myCtorBuilder.GetILGenerator();<\/p>\n<p>Once you have an ILGenerator in your hands, you are then able to emit the raw CIL opcodes using any number of methods. Table 18-8 documents some (but not all) methods of ILGenerator.<\/p>\n<p>Table 18-8. Various Methods of ILGenerator<\/p>\n<p>Method  Meaning in Life<br \/>\nBeginCatchBlock()   Begins a catch block<br \/>\nBeginExceptionBlock()   Begins an exception scope for an exception<br \/>\nBeginFinallyBlock() Begins a finally block<br \/>\nBeginScope()    Begins a lexical scope<br \/>\nDeclareLocal()  Declares a local variable<br \/>\nDefineLabel()   Declares a new label<br \/>\nEmit()  Is overloaded numerous times to allow you to emit CIL opcodes<br \/>\nEmitCall()  Pushes a call or callvirt opcode into the CIL stream<br \/>\nEmitWriteLine() Emits a call to Console.WriteLine() with different types of values<br \/>\nEndExceptionBlock() Ends an exception block<br \/>\nEndScope()  Ends a lexical scope<br \/>\nThrowException()    Emits an instruction to throw an exception<br \/>\nUsingNamespace()    Specifies the namespace to be used in evaluating locals and watches for the current active lexical scope<\/p>\n<p>The key method of ILGenerator is Emit(), which works in conjunction with the System.Reflection. Emit.OpCodes class type. As mentioned earlier in this chapter, this type exposes a good number of read-only fields that map to raw CIL opcodes. The full set of these members is documented within online help, and you will see various examples in the pages that follow.<\/p>\n<p>Emitting a Dynamic Assembly<br \/>\nTo illustrate the process of defining a .NET Core assembly at runtime, let\u2019s walk through the process of creating a single-file dynamic assembly. Within this assembly is a class named HelloWorld. The HelloWorld class supports a default constructor and a custom constructor that is used to assign the value of a private member variable (theMessage) of type string. In addition, HelloWorld supports a public instance method named SayHello(), which prints a greeting to the standard I\/O stream, and another instance method named GetMsg(), which returns the internal private string. In effect, you are going to programmatically generate the following class type:<\/p>\n<p>\/\/ This class will be created at runtime<br \/>\n\/\/ using System.Reflection.Emit.<br \/>\npublic class HelloWorld<br \/>\n{<br \/>\nprivate string theMessage; HelloWorld() {}<br \/>\nHelloWorld(string s) {theMessage = s;}<\/p>\n<p>public string GetMsg() {return theMessage;} public void SayHello()<br \/>\n{<br \/>\nSystem.Console.WriteLine(&quot;Hello from the HelloWorld class!&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Assume you have created a new Console Application project named DynamicAsmBuilder and you add the System.Reflection.Emit NuGet package. Next, import the System.Reflection and System. Reflection.Emit namespaces. Define a static method named CreateMyAsm() in the Program.cs file. This single method oversees the following:<br \/>\n\u2022   Defining the characteristics of the dynamic assembly (name, version, etc.)<br \/>\n\u2022   Implementing the HelloClass type<br \/>\n\u2022   Returning the AssemblyBuilder to the calling method Here is the complete code, with analysis to follow:<br \/>\nstatic AssemblyBuilder CreateMyAsm()<br \/>\n{<br \/>\n\/\/ Establish general assembly characteristics.<br \/>\nAssemblyName assemblyName = new AssemblyName<br \/>\n{<br \/>\nName = &quot;MyAssembly&quot;,<br \/>\nVersion = new Version(&quot;1.0.0.0&quot;)<br \/>\n};<\/p>\n<p>\/\/ Create new assembly.<br \/>\nvar builder = AssemblyBuilder.DefineDynamicAssembly( assemblyName,AssemblyBuilderAccess.Run);<\/p>\n<p>\/\/ Define the name of the module.<br \/>\nModuleBuilder module =<\/p>\n<p>builder.DefineDynamicModule(&quot;MyAssembly&quot;);<br \/>\n\/\/ Define a public class named &quot;HelloWorld&quot;.<br \/>\nTypeBuilder helloWorldClass = module.DefineType(&quot;MyAssembly.HelloWorld&quot;, TypeAttributes.Public);<\/p>\n<p>\/\/ Define a private String variable named &quot;theMessage&quot;.<br \/>\nFieldBuilder msgField = helloWorldClass.DefineField( &quot;theMessage&quot;,<br \/>\nType.GetType(&quot;System.String&quot;), attributes: FieldAttributes.Private);<\/p>\n<p>\/\/ Create the custom ctor.<br \/>\nType[] constructorArgs = new Type[1]; constructorArgs[0] = typeof(string); ConstructorBuilder constructor =<br \/>\nhelloWorldClass.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, constructorArgs);<br \/>\nILGenerator constructorIl = constructor.GetILGenerator(); constructorIl.Emit(OpCodes.Ldarg_0);<br \/>\nType objectClass = typeof(object); ConstructorInfo superConstructor =<br \/>\nobjectClass.GetConstructor(new Type[0]); constructorIl.Emit(OpCodes.Call, superConstructor); constructorIl.Emit(OpCodes.Ldarg_0); constructorIl.Emit(OpCodes.Ldarg_1); constructorIl.Emit(OpCodes.Stfld, msgField); constructorIl.Emit(OpCodes.Ret);<\/p>\n<p>\/\/ Create the default constructor.<br \/>\nhelloWorldClass.DefineDefaultConstructor( MethodAttributes.Public);<br \/>\n\/\/ Now create the GetMsg() method.<br \/>\nMethodBuilder getMsgMethod = helloWorldClass.DefineMethod( &quot;GetMsg&quot;,<br \/>\nMethodAttributes.Public, typeof(string),<br \/>\nnull);<br \/>\nILGenerator methodIl = getMsgMethod.GetILGenerator(); methodIl.Emit(OpCodes.Ldarg_0); methodIl.Emit(OpCodes.Ldfld, msgField); methodIl.Emit(OpCodes.Ret);<\/p>\n<p>\/\/ Create the SayHello method.<br \/>\nMethodBuilder sayHiMethod = helloWorldClass.DefineMethod( &quot;SayHello&quot;, MethodAttributes.Public, null, null);<br \/>\nmethodIl = sayHiMethod.GetILGenerator(); methodIl.EmitWriteLine(&quot;Hello from the HelloWorld class!&quot;); methodIl.Emit(OpCodes.Ret);<\/p>\n<p>\/\/ &quot;Bake&quot; the class HelloWorld.<br \/>\n\/\/ (Baking is the formal term for emitting the type.) helloWorldClass.CreateType();<\/p>\n<p>return builder;<br \/>\n}<\/p>\n<p>Emitting the Assembly and Module Set<br \/>\nThe method body begins by establishing the minimal set of characteristics about your assembly, using the AssemblyName and Version types (defined in the System.Reflection namespace). Next, you obtain an AssemblyBuilder type via the static AssemblyBuilder.DefineDynamicAssembly() method.<br \/>\nWhen calling DefineDynamicAssembly(), you must specify the access mode of the assembly you want to define, the most common values of which are shown in Table 18-9.<\/p>\n<p>Table 18-9. Common Values of the AssemblyBuilderAccess Enumeration<\/p>\n<p>Value   Meaning in Life<br \/>\nRunAndCollect   The assembly will be immediately unloaded, and its memory is reclaimed once it is no longer accessible.<br \/>\nRun This represents that a dynamic assembly can be executed in memory but not saved to disk.<\/p>\n<p>The next task is to define the module set (and its name) for your new assembly. Once the DefineDynamicModule() method has returned, you are provided with a reference to a valid ModuleBuilder type.<\/p>\n<p>\/\/ Create new assembly.<br \/>\nvar builder = AssemblyBuilder.DefineDynamicAssembly( assemblyName,AssemblyBuilderAccess.Run);<\/p>\n<p>The Role of the ModuleBuilder TypeC<br \/>\nModuleBuilder is the key type used during the development of dynamic assemblies. As you would expect, ModuleBuilder supports several members that allow you to define the set of types contained within a given module (classes, interfaces, structures, etc.) as well as the set of embedded resources (string tables, images, etc.) contained within. Table 18-10 describes two of the creation-centric methods. (Do note that each method will return to you a related type that represents the type you want to construct.)<\/p>\n<p>Table 18-10. Select Members of the ModuleBuilder Type<\/p>\n<p>Method  Meaning in Life<br \/>\nDefineEnum()    Used to emit a .NET enum definition<br \/>\nDefineType()    Constructs a TypeBuilder, which allows you to define value types, interfaces, and class types (including delegates)<\/p>\n<p>The key member of the ModuleBuilder class to be aware of is DefineType(). In addition to specifying the name of the type (via a simple string), you will also use the System.Reflection.TypeAttributes enum to describe the format of the type itself. Table 18-11 lists some (but not all) of the key members of the TypeAttributes enumeration.<\/p>\n<p>Table 18-11. Select Members of the TypeAttributes Enumeration<\/p>\n<p>Member  Meaning in Life<br \/>\nAbstract    Specifies that the type is abstract<br \/>\nClass   Specifies that the type is a class<br \/>\nInterface   Specifies that the type is an interface<br \/>\nNestedAssembly  Specifies that the class is nested with assembly visibility and is thus accessible only by methods within its assembly<br \/>\nNestedFamANDAssem   Specifies that the class is nested with assembly and family visibility and is thus accessible only by methods lying in the intersection of its family and assembly<br \/>\nNestedFamily    Specifies that the class is nested with family visibility and is thus accessible only by methods within its own type and any subtypes<br \/>\nNestedFamORAssem    Specifies that the class is nested with family or assembly visibility and is thus accessible only by methods lying in the union of its family and assembly<br \/>\nNestedPrivate   Specifies that the class is nested with private visibility<br \/>\nNestedPublic    Specifies that the class is nested with public visibility<br \/>\nNotPublic   Specifies that the class is not public<br \/>\nPublic  Specifies that the class is public<br \/>\nSealed  Specifies that the class is concrete and cannot be extended<br \/>\nSerializable    Specifies that the class can be serialized<\/p>\n<p>Emitting the HelloClass Type and the String Member Variable<br \/>\nNow that you have a better understanding of the role of the ModuleBuilder.CreateType() method, let\u2019s examine how you can emit the public HelloWorld class type and the private string variable.<\/p>\n<p>\/\/ Define a public class named &quot;HelloWorld&quot;.<br \/>\nTypeBuilder helloWorldClass = module.DefineType(&quot;MyAssembly.HelloWorld&quot;, TypeAttributes.Public);<\/p>\n<p>\/\/ Define a private String variable named &quot;theMessage&quot;.<br \/>\nFieldBuilder msgField = helloWorldClass.DefineField( &quot;theMessage&quot;,<br \/>\nType.GetType(&quot;System.String&quot;), attributes: FieldAttributes.Private);<\/p>\n<p>Notice how the TypeBuilder.DefineField() method provides access to a FieldBuilder type. The TypeBuilder class also defines other methods that provide access to other \u201cbuilder\u201d types. For example, DefineConstructor() returns a ConstructorBuilder, DefineProperty() returns a PropertyBuilder, and so forth.<\/p>\n<p>Emitting the Constructors<br \/>\nAs mentioned earlier, the TypeBuilder.DefineConstructor() method can be used to define a constructor for the current type. However, when it comes to implementing the constructor of HelloClass, you need to inject raw CIL code into the constructor body, which is responsible for assigning the incoming parameter to the internal private string. To obtain an ILGenerator type, you call the GetILGenerator() method from the respective \u201cbuilder\u201d type you have reference to (in this case, the ConstructorBuilder type).<br \/>\nThe Emit() method of the ILGenerator class is the entity in charge of placing CIL into a member implementation. Emit() itself makes frequent use of the OpCodes class type, which exposes the opcode set of CIL using read-only fields. For example, OpCodes.Ret signals the return of a method call, OpCodes.Stfld makes an assignment to a member variable, and OpCodes.Call is used to call a given method (in this case, the base class constructor). That said, ponder the following constructor logic:<\/p>\n<p>\/\/ Create the custom ctor taking single string arg. Type[] constructorArgs = new Type[1]; constructorArgs[0] = typeof(string); ConstructorBuilder constructor =<br \/>\nhelloWorldClass.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, constructorArgs);<br \/>\n\/\/Emit the necessary CIL into the ctor<br \/>\nILGenerator constructorIl = constructor.GetILGenerator(); constructorIl.Emit(OpCodes.Ldarg_0);<br \/>\nType objectClass = typeof(object); ConstructorInfo superConstructor =<br \/>\nobjectClass.GetConstructor(new Type[0]); constructorIl.Emit(OpCodes.Call, superConstructor);<br \/>\n\/\/Load this pointer onto the stack constructorIl.Emit(OpCodes.Ldarg_0); constructorIl.Emit(OpCodes.Ldarg_1);<br \/>\n\/\/Load argument on virtual stack and store in msdField constructorIl.Emit(OpCodes.Stfld, msgField); constructorIl.Emit(OpCodes.Ret);<\/p>\n<p>Now, as you are aware, as soon as you define a custom constructor for a type, the default constructor is silently removed. To redefine the no-argument constructor, simply call the DefineDefaultConstructor() method of the TypeBuilder type as follows:<\/p>\n<p>\/\/ Create the default ctor.<br \/>\nhelloWorldClass.DefineDefaultConstructor( MethodAttributes.Public);<\/p>\n<p>Emitting the SayHello() Method<br \/>\nFinally, let\u2019s examine the process of emitting the SayHello() method. The first task is to obtain a MethodBuilder type from the helloWorldClass variable. After you do this, you define the method and obtain the underlying ILGenerator to inject the CIL instructions, like so:<\/p>\n<p>\/\/ Create the SayHello method.<br \/>\nMethodBuilder sayHiMethod = helloWorldClass.DefineMethod( &quot;SayHello&quot;, MethodAttributes.Public, null, null);<br \/>\nmethodIl = sayHiMethod.GetILGenerator();<\/p>\n<p>\/\/Write to the console<br \/>\nmethodIl.EmitWriteLine(&quot;Hello from the HelloWorld class!&quot;); methodIl.Emit(OpCodes.Ret);<\/p>\n<p>Here you have established a public method (MethodAttributes.Public) that takes no parameters and returns nothing (marked by the null entries contained in the DefineMethod() call). Also note the EmitWriteLine() call. This helper member of the ILGenerator class automatically writes a line to the standard output with minimal fuss and bother.<\/p>\n<p>Using the Dynamically Generated Assembly<br \/>\nNow that you have the logic in place to create your assembly, all that is needed is to execute the generated code. The logic in the calling code calls the CreateMyAsm() method, getting a reference to the created AssemblyBuilder.<br \/>\nNext, you will exercise some late binding (see Chapter 17) to create an instance of the HelloWorld class and interact with its members. Update your top-level statements as follows:<\/p>\n<p>using System.Reflection; using System.Reflection.Emit;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> The Amazing Dynamic Assembly Builder App <\/em><\/strong><\/strong>&quot;);<br \/>\n\/\/ Create the assembly builder using our helper f(x). AssemblyBuilder builder = CreateMyAsm();<\/p>\n<p>\/\/ Get the HelloWorld type.<br \/>\nType hello = builder.GetType(&quot;MyAssembly.HelloWorld&quot;);<\/p>\n<p>\/\/ Create HelloWorld instance and call the correct ctor. Console.Write(&quot;-&gt; Enter message to pass HelloWorld class: &quot;); string msg = Console.ReadLine();<br \/>\nobject[] ctorArgs = new object[1]; ctorArgs[0] = msg;<br \/>\nobject obj = Activator.CreateInstance(hello, ctorArgs);<\/p>\n<p>\/\/ Call SayHello and show returned string. Console.WriteLine(&quot;-&gt; Calling SayHello() via late binding.&quot;); MethodInfo mi = hello.GetMethod(&quot;SayHello&quot;);<\/p>\n<p>mi.Invoke(obj, null);<\/p>\n<p>\/\/ Invoke method.<br \/>\nmi = hello.GetMethod(&quot;GetMsg&quot;); Console.WriteLine(mi.Invoke(obj, null));<\/p>\n<p>In effect, you have just created a .NET Core assembly that is able to create and execute .NET Core assemblies at runtime! That wraps up the examination of CIL and the role of dynamic assemblies. I hope this chapter has deepened your understanding of the .NET Core type system, the syntax and semantics of CIL, and how the C# compiler processes your code at compile time.<\/p>\n<p>Summary<br \/>\nThis chapter provided an overview of the syntax and semantics of CIL. Unlike higher-level managed languages such as C#, CIL does not simply define a set of keywords but provides directives (used to define the structure of an assembly and its types), attributes (which further qualify a given directive), and opcodes (which are used to implement type members).<br \/>\nYou were introduced to a few CIL-centric programming tools and learned how to alter the contents of a .NET assembly with new CIL instructions using round-trip engineering. After this point, you spent time learning how to establish the current (and referenced) assembly, namespaces, types, and members. I<br \/>\nwrapped up with a simple example of building a .NET code library and executable using little more than CIL, command-line tools, and a bit of elbow grease.<br \/>\nFinally, you took an introductory look at the process of creating a dynamic assembly. Using the System. Reflection.Emit namespace, it is possible to define a .NET Core assembly in memory at runtime. As you have seen firsthand, using this API requires you to know the semantics of CIL code in some detail. While the need to build dynamic assemblies is certainly not a common task for most .NET Core applications, it can be useful for those of you who need to build support tools and other programming utilities.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>CHAPTER 18 Understanding CIL and the Role of Dynamic Assemblies When you are building a full-scale .NET application, you will most certainly use C# (or a similar managed language such as Visual Basic), given its inherent productivity and ease of use. However, as you learned in the beginning of this book, the role of a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[22],"class_list":["post-334","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-pro-csharp10-with-net6"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/334","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=334"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/334\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=334"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=334"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=334"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}