Exploring the .NET Core 3.0 Runtime 9781484251126

834 125 4MB

English Pages 190 Year 2019

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Exploring the .NET Core 3.0 Runtime
 9781484251126

  • Commentary
  • Better PDF than that with ID 2403370, created by an asshole who cut the edge and remove some pages

Table of contents :
Table of Contents......Page 5
About the Author......Page 8
About the Technical Reviewer......Page 9
Acknowledgments......Page 10
Introduction......Page 11
Acronyms......Page 12
About the .NET Core Platform......Page 13
Microsoft Visual Studio 2019 Support for .NET Core......Page 16
Configuration for Using the .NET Core SDK Previews......Page 19
Adding a Class Library (.NET Core) Project......Page 20
Changing the Project Config File......Page 21
Using .NET Standard......Page 23
List of TFMs......Page 25
Do’s......Page 44
Don’ts......Page 45
Acronyms......Page 46
Development Environment......Page 47
The Static .NET Assembly......Page 48
Metadata System......Page 52
Metadata System Organization......Page 53
About the .NET Assembly Manifest......Page 65
Do’s......Page 90
Don’ts......Page 92
About Dynamic Assemblies......Page 93
Purpose of .NET Types......Page 99
Builders of the System.Reflection.Emit Namespace......Page 100
The Flags and Definition of .NET Types......Page 110
Using an Assembly Type Builder......Page 112
Naming the Dynamic .NET Assembly......Page 113
AssemblyBuilderAccess Enum Options......Page 114
Defining a Dynamic .NET Type......Page 118
Defining Dynamic .NET Field Members for a Dynamic .NET Type......Page 122
Instantiating a Dynamic .NET Type and  Assigning a Value for a Dynamic .NET Field......Page 125
Do’s......Page 127
Don’ts......Page 129
The Getter and Setter Methods......Page 130
The Other Methods......Page 131
The Prefixes get_ and set_......Page 133
The specialname and rtspecialname Attributes......Page 134
Implementing a Dynamic .NET Property......Page 147
Don’ts......Page 162
About the CodeDOM......Page 163
Code Generation Using the CodeDOM......Page 164
Summary......Page 184
Don’ts......Page 185
Index......Page 186

Citation preview

Exploring the .NET Core 3.0 Runtime Through Code Generation and Metadata Inspection — Roger Villela

Exploring the .NET Core 3.0 Runtime Through Code Generation and Metadata Inspection

Roger Villela

Exploring the .NET Core 3.0 Runtime: Through Code Generation and Metadata Inspection Roger Villela Sao Paulo, São Paulo, Brazil ISBN-13 (pbk): 978-1-4842-5112-6 https://doi.org/10.1007/978-1-4842-5113-3

ISBN-13 (electronic): 978-1-4842-5113-3

Copyright © 2019 by Roger Villela This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed. Trademarked names, logos, and images may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, logo, or image we use the names, logos, and images only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. The use in this publication of trade names, trademarks, service marks, and similar terms, even if they are not identified as such, is not to be taken as an expression of opinion as to whether or not they are subject to proprietary rights. While the advice and information in this book are believed to be true and accurate at the date of publication, neither the authors nor the editors nor the publisher can accept any legal responsibility for any errors or omissions that may be made. The publisher makes no warranty, express or implied, with respect to the material contained herein. Managing Director, Apress Media LLC: Welmoed Spahr Acquisitions Editor: Smriti Srivastava Development Editor: Matthew Moodie Coordinating Editor: Shrikant Vishwakarma Cover designed by eStudioCalamar Cover image designed by Freepik (www.freepik.com) Distributed to the book trade worldwide by Springer Science+Business Media New York, 233 Spring Street, 6th Floor, New York, NY 10013. Phone 1-800-SPRINGER, fax (201) 348-4505, e-mail [email protected], or visit www.springeronline.com. Apress Media, LLC is a California LLC and the sole member (owner) is Springer Science + Business Media Finance Inc (SSBM Finance Inc). SSBM Finance Inc is a Delaware corporation. For information on translations, please e-mail [email protected], or visit www.apress.com/ rights-permissions. Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook versions and licenses are also available for most titles. For more information, reference our Print and eBook Bulk Sales web page at www.apress.com/bulk-sales. Any source code or other supplementary material referenced by the author in this book is available to readers on GitHub via the book’s product page, located at www.apress.com/978-1-4842-5112-6. For more detailed information, please visit www.apress.com/source-code. Printed on acid-free paper

This book is dedicated to my mother, Marina Roel de Oliveira (†). † From 1952, January 14, to 2017, March 17

Table of Contents About the Author���������������������������������������������������������������������������������ix About the Technical Reviewer�������������������������������������������������������������xi Acknowledgments�����������������������������������������������������������������������������xiii Introduction����������������������������������������������������������������������������������������xv Chapter 1: Exploring .NET Core�������������������������������������������������������������1 Acronyms��������������������������������������������������������������������������������������������������������������1 About the .NET Core Platform�������������������������������������������������������������������������������2 Microsoft Visual Studio 2019 Support for .NET Core���������������������������������������������5 Tutorial: Using .NET Core SDK Previews and configurations���������������������������������8 Configuration for Using the .NET Core SDK Previews��������������������������������������8 Adding a Class Library (.NET Core) Project������������������������������������������������������9 Choosing the Target .NET Core SDK���������������������������������������������������������������10 Changing the Project Config File�������������������������������������������������������������������10 Using a TFM on the Project Config File����������������������������������������������������������12 Using .NET Standard��������������������������������������������������������������������������������������12 List of TFMs���������������������������������������������������������������������������������������������������14 Summary������������������������������������������������������������������������������������������������������������33 Do’s����������������������������������������������������������������������������������������������������������������33 Don’ts������������������������������������������������������������������������������������������������������������34

v

Table of Contents

Chapter 2: About Static .NET Assembly����������������������������������������������35 Acronyms������������������������������������������������������������������������������������������������������������35 Development Environment����������������������������������������������������������������������������������36 The Static .NET Assembly�����������������������������������������������������������������������������������37 Metadata System������������������������������������������������������������������������������������������41 About the .NET Assembly Manifest����������������������������������������������������������������54 Summary������������������������������������������������������������������������������������������������������������79 Do’s����������������������������������������������������������������������������������������������������������������79 Don’ts������������������������������������������������������������������������������������������������������������81

Chapter 3: Dynamic .NET Assemblies: Defining Dynamic .NET Types������������������������������������������������������������������������������������������83 About Dynamic Assemblies���������������������������������������������������������������������������������83 Organization of RVJ.Core .NET Types������������������������������������������������������������������89 Purpose of .NET Types�����������������������������������������������������������������������������������89 .NET Type Interfaces��������������������������������������������������������������������������������������90 Builders of the System.Reflection.Emit Namespace�������������������������������������90 The Flags and Definition of .NET Types�������������������������������������������������������100 About System.Reflection.Emit Type Builders����������������������������������������������������102 Using an Assembly Type Builder������������������������������������������������������������������102 Naming the Dynamic .NET Assembly����������������������������������������������������������103 AssemblyBuilderAccess Enum Options�������������������������������������������������������104 Defining a Dynamic .NET Type���������������������������������������������������������������������108 Defining Dynamic .NET Field Members for a Dynamic .NET Type���������������������112 Instantiating a Dynamic .NET Type and Assigning a Value for a Dynamic .NET Field����������������������������������������������������������������������������������������115 Summary����������������������������������������������������������������������������������������������������������117 Do’s��������������������������������������������������������������������������������������������������������������117 Don’ts����������������������������������������������������������������������������������������������������������119 vi

Table of Contents

Chapter 4: Working with Dynamic .NET Properties��������������������������121 About Dynamic .NET Properties������������������������������������������������������������������������121 The Getter and Setter Methods�������������������������������������������������������������������121 The Other Methods��������������������������������������������������������������������������������������122 The Prefixes get_ and set_��������������������������������������������������������������������������124 The specialname and rtspecialname Attributes������������������������������������������125 Implementing a Dynamic .NET Property�����������������������������������������������������������138 Summary����������������������������������������������������������������������������������������������������������153 Do’s��������������������������������������������������������������������������������������������������������������153 Don’ts����������������������������������������������������������������������������������������������������������153

Chapter 5: Working with the CodeDOM��������������������������������������������155 About the CodeDOM������������������������������������������������������������������������������������������155 Code Generation Using the CodeDOM���������������������������������������������������������������156 Summary����������������������������������������������������������������������������������������������������������176 Do’s��������������������������������������������������������������������������������������������������������������177 Don’ts����������������������������������������������������������������������������������������������������������177

Index�������������������������������������������������������������������������������������������������179

vii

About the Author Roger Villela is an independent software engineer and entrepreneur with 30 years of experience. Currently, he writes books and educates people about technology, specializing in the inner works of the following Microsoft development platforms:

–– Microsoft Windows operating system base services –– Microsoft Universal Windows Platform (UWP) –– Microsoft WinRT –– Microsoft .NET Framework implementation of the runtime environment (CLR) Roger’s work is based on Microsoft Visual Studio (Microsoft Windows) and Intel Parallel Studio XE (Microsoft Windows), using the following programming languages, extensions, and projections: –– C++ –– Assembly (Intel IA-32/Intel 64) –– Component extensions for runtimes (C++/CLI and C++/CX)

ix

About the Technical Reviewer Carsten Thomsen is primarily a back-end developer but works with smaller, front-end bits as well. He has authored and reviewed a number of books and has created numerous Microsoft Learning courses, all related to software development. He works as a freelancer/contractor in various countries in Europe, using Azure, Visual Studio, Azure DevOps, and GitHub. Being an exceptional troubleshooter who asks the right questions, he also enjoys architecture, research, analysis, development, testing, and bug fixing. Carsten is an excellent communicator with great mentoring and team-lead skills and excels at researching and presenting new material.  

xi

Acknowledgments First, I would like to thank the team at Apress who worked with me on this book: Smriti Srivastava (acquisitions editor), Shrikant Vishwakarma (coordinating editor), Matthew Moodie (development editor), Welmoed Spahr (managing director), and Carsten Thomsen (technical reviewer). It was a pleasure and an honor to work with such a highly professional team. Thanks to both my parents and a special thanks to my dad, Gilberto. Thanks to my aunts, Marinete and Maria Águida; to my cousin, Silvana; and to my brother Eder and my brother Marlos and his wife, Janaína, as well as my nephew, Gabriel, and nieces, Lívia and Rafaela. I would also like to thank my professional colleagues and friends who have worked with me for the past decade.

xiii

Introduction Software engineering is a challenge and a pleasure, and in this book, you will learn how to program for two advanced features that are available in the .NET Core 3.0 environment: the .NET API for the dynamic generation of code and the .NET API for metadata inspection. This knowledge is the starting point for designing and implementing a library that can be used by a code generator tool and a metadata inspector tool. This library will be able to be used on different operating system platforms, because .NET Core 3.0 is a cross-platform implementation of ECMA-335. All the source code is written in the C# programming language, and the code uses only cross-platform Base Class Library (BCL) types. The .NET API for code generation can help you improve your productivity and the quality of your .NET software by automating repetitive tasks. In this book, you also will learn about the internal aspects of the Virtual Execution System (VES), which is the virtual machine (a virtual hardware platform) of the .NET platform that, among other tasks, runs the intermediate code. You will learn about the garbage collection (GC) mechanism that is responsible for automatic memory management, one of the features of the .NET Core platform. The .NET API for metadata inspection is another valuable .NET Core 3.0 feature, especially for internal tools that are used to validate assemblies in certain business scenarios. For example, in a .NET hierarchy, every reference type or value type implicitly inherits from the System. Object root class type, and in this hierarchy of business objects, you will probably also have a root class from which all reference types should inherit. You can use the metadata inspection feature to check the business implementation rules based on the configuration files.

xv

CHAPTER 1

Exploring .NET Core This chapter gives you an overview of the .NET Core platform and the features of Microsoft Visual Studio 2019 specific to the environment.

A  cronyms The following are the acronyms introduced in this chapter and used throughout the book: •

Application programming interface (API)



Base Class Library (BCL)



Common Type System (CTS)



Common Intermediate Language (CIL)



Common Language Infrastructure (CLI)



Common Language Runtime (CLR)



Common Language Specification (CLS)



Framework Class Library (FCL)



General availability (GA)



Intermediate language (IL)



Just-in-time (JIT)



Target framework moniker (TFM)

© Roger Villela 2019 R. Villela, Exploring the .NET Core 3.0 Runtime, https://doi.org/10.1007/978-1-4842-5113-3_1

1

Chapter 1

Exploring .NET Core



Long-term support (LTS)



Microsoft Intermediate Language (MSIL)



Release candidate (RC)



Virtual Execution System (VES)

About the .NET Core Platform .NET Core is a general-purpose, .NET cross-platform development framework that provides support for Microsoft Windows, Apple macOS, and Linux distributions. Like the .NET Framework, the .NET Core implementation is based on the ECMA-335 specification. The .NET Core project is available on GitHub; it is open source and maintained by Microsoft and the .NET community. With the .NET Core platform, it is possible to write applications, libraries, and components for desktop development, web development, cloud development, device development, and IoT applications. Here are some examples of GitHub repositories, including WPF and Windows Forms, that are now officially .NET Core–based UI frameworks: •

GitHub repository for .NET Core (https://github. com/dotnet/core) •

2

.NET Core is a self-contained .NET runtime and framework that implements ECMA-335. It can be (and has been) ported to multiple architectures and platforms. It supports a variety of installation options and has no specific deployment requirements itself. This repo includes several documents that explain both high-level and low-level concepts about the .NET runtime. These documents are particularly useful for contributors to get context that can be difficult to acquire from just reading code.

Chapter 1



GitHub repository for .NET Core Foundational Class Libraries, the BCL and FCL (https://github.com/ dotnet/corefx) •



The .NET platform has a standard set of class libraries. The BCL (core set) is expected with any .NET implementation, because without it, you would not have a functional implementation of .NET. The FCL (complete set) is not fully required, but these two libraries provide .NET types for many general and app-­specific types. Commercial and community libraries can be built on top of the BCL and FCL libraries. The CoreFX repository contains both, the BCL and the FCL.

GitHub repository for the .NET Core runtime, the CoreCLR (https://github.com/dotnet/coreclr) •



Exploring .NET Core

This is the runtime for .NET Core; it is composed of the garbage collector, JIT compiler, primitive data types, and low-level classes. The .NET Core runtime implements the ECMA-335 specification, is a self-­ contained .NET runtime and framework, has been ported to multiple architectures and platforms, and, having no specific deployment requirements itself, supports a variety of installation options.

GitHub repository for .NET WPF UI framework (https://github.com/dotnet/wpf) •

The WPF is now officially a .NET Core–based UI framework for the development of applications and components for the Microsoft Windows desktop. It runs exclusively on the Microsoft Windows family of operating systems and relies on Microsoft 3

Chapter 1

Exploring .NET Core

DirectX technologies. It has a vector-based graphics architecture that enables the use of high-DPI monitors and infinity scale and uses the Extensible Application Markup Language (XAML) to provide a declarative model for application programming. •

GitHub repository for .NET Core Windows Forms UI framework (https://github.com/dotnet/winforms) •



Windows Forms is now officially a .NET Core–based UI framework for the development of applications and components for the Microsoft Windows desktop. The Windows Forms UI framework runs exclusively on the Microsoft Windows family of operating systems and relies on Microsoft Windows GDI+ technology.

GitHub repository for ASP.NET Core (https://github. com/aspnet/AspNetCore) •

ASP.NET Core is an open source, cross-platform framework for building web applications, cloudbased applications, IoT applications, and back-end services for mobile applications. It can be hosted on Windows, Mac, or Linux and can be deployed in the cloud or on-­premises. For .NET Core 3.0, Microsoft has officially scheduled the .NET Core 3.0 RC for July 2019, the .NET Core 3.0 GA for September 2019, and the .NET Core 3.1 LTS for November 2019. Microsoft is also reorganizing Microsoft .NET, and by 2020, there will be only one .NET Framework, not the .NET Framework and .NET Core. You can learn more in the post “Introducing .NET 5” at ­https:// devblogs.microsoft.com/dotnet/introducing-net-5/.

4

Chapter 1

Exploring .NET Core

According to an official chronogram, Microsoft has scheduled the new .NET 5.0 (GA) for November 2020, .NET 6.0 (LTS) for November 2021, .NET 7.0 (GA) for November 2022, and .NET 8.0 (LTS) for November 2023. Now let’s talk about Microsoft Visual Studio 2019 support for .NET Core.

 icrosoft Visual Studio 2019 Support for M .NET Core Microsoft offers great support within the Microsoft Visual Studio IDE for .NET Core development. The images and comments in this section are based on features of Microsoft Visual Studio 2019 RTM (Community, Professional, and Enterprise) version 16.1.4. If you’re using Microsoft .NET Core 3.0 preview version (preview 6 at the time of this writing), Microsoft advocates using Microsoft Visual Studio 2019 version 16.0 as the minimum version. Here’s a list of minimum versions to use for Microsoft Visual Studio 2019 and Microsoft Visual Studio 2017: •

Microsoft Visual Studio 2019 version 16.0 or higher for developing applications and components targeting .NET Core 3.0 (preview 6 at the time of this writing)



Microsoft Visual Studio 2017 version 15.9 or higher for developing applications and components targeting .NET Core 2.2



Microsoft Visual Studio 2017 version 15.7 or higher for developing applications and components targeting .NET Core 2.1

5

Chapter 1



Exploring .NET Core

Microsoft Visual Studio 2017 version 15.0 or higher for developing applications and components targeting .NET Core 1.x

Figure 1-1 shows Microsoft Visual Studio 2019’s new start window with choices for creating projects using project templates and opening recent projects and solutions. You can also apply filters for programming language, platform, and project type.

Figure 1-1.  Microsoft Visual Studio 2019’s new start window for creating, opening, and connecting to a source repository At the time of writing, the following are examples of templates for .NET Core in Microsoft Visual Studio 2019:

6



ASP.NET Core Web Application



Class Library (.NET Core)

Chapter 1



Console App (.NET Core)



MSTest Test Project (.NET Core)



NUnit Test Project (.NET Core)



Web Driver Test for Edge (.NET Core)



Windows Forms App (.NET Core)



WPF App (.NET Core)



xUnit Test Project (.NET Core)

Exploring .NET Core

Figure 1-2 shows the start window with the Console App (.NET Core) template selected. You can also see the templates called ASP.NET Core Web Application and WPF App (.NET Core).

Figure 1-2.  Microsoft Visual Studio 2019’s start window showing the list of project templates

7

Chapter 1

Exploring .NET Core

T utorial: Using .NET Core SDK Previews and configurations In this section, I will show you how to use some templates for .NET Core projects and how to configure Microsoft Visual Studio 2019 to use the .NET Core preview implementations.

 onfiguration for Using the .NET Core SDK C Previews To use the .NET Core SDKs in a preview version, you need to change the configuration of Microsoft Visual Studio 2019. Select the Tools ➤ Options menu item to open the Options dialog. Open Environment on the left and select Preview Features. Select “Use previews of the .NET Core SDK, as shown in Figure 1-3. This allows Microsoft Visual Studio 2019 to include the .NET Core SDK preview versions in the list of target frameworks for the projects.

Figure 1-3.  The Options dialog, showing the “Use previews of the .NET Core SDK” setting 8

Chapter 1

Exploring .NET Core

Adding a Class Library (.NET Core) Project Now, with the configuration for the .NET Core SDK previews done, open the start window. For this tutorial, choose the Class Library (.NET Core) template and name it RVJ.Core. For the path, I’m using a temporary folder, as shown in Figure 1-­4, because this project is just a demonstration. If you want to get the solution and projects for this tutorial, you can find them in this folder: \Projects\RVJ\2019\Platforms\CLR\Code\ ExploringDotNETCoreRuntime30\Ch01\Tutorial\.

Figure 1-4.  Specifying the name of the project and solution, as well as the location

9

Chapter 1

Exploring .NET Core

After the project has been created, you’ll see the standard development environment that you probably know from previous versions for developing applications, libraries, and components for the Microsoft .NET platform.

Choosing the Target .NET Core SDK On the Application page of the Project Properties window, you’ll see the “Target framework” list with all the .NET Core SDKs available, including the preview versions, as shown in Figure 1-5.

Figure 1-5.  The list of the .NET Core SDKs available, including the preview versions

Changing the Project Config File In the Solution Explorer, double-click the project name to open the content of the project file in the source code editor, as shown in Figure 1-6. There’s also IntelliSense support for project files, as shown in Figure 1-7.

10

Chapter 1

Exploring .NET Core

Figure 1-6.  Content of the RVJ.Core.csproj project file

Figure 1-7.  IntelliSense is available for the content of .NET Core project files

11

Chapter 1

Exploring .NET Core

Using a TFM on the Project Config File As shown in Figure 1-8, when specifying the target framework for .NET Core, you can use a TFM in the project file. The TFM specifies the target framework and, consequently, the sets of APIs available for an application or library.

Figure 1-8.  The sets of APIs used for an application or library are specified using TFMs for the target framework

Using .NET Standard The .NET Core or .NET Framework applications and libraries can also target a version of .NET Standard, which is a standardized set of APIs that work across all .NET implementations. Using a library such as the RVJ. Core sample project, it’s possible to target .NET Standard and gain access to APIs that work across .NET Core and .NET Framework using the same codebase. In Listing 1-1, I have changed the RVJ.Core.csproj project file to use .NET Standard version 2.1.

12

Chapter 1

Exploring .NET Core

Listing 1-1.  Configuration File Using .NET Standard 2.1

            netstandard2.1   

After this update, the “Target framework” field on the Application page of the Project Properties window is automatically updated, as shown in Figure 1-9.

Figure 1-9.  The “Target framework” field on the Application page of the Project Properties window is automatically updated

13

Chapter 1

Exploring .NET Core

List of TFMs At the time of writing, the following TFMs are currently supported by project files: •





14

.NET Standard: •

netstandard1.0



netstandard1.1



netstandard1.2



netstandard1.3



netstandard1.4



netstandard1.5



netstandard1.6



netstandard2.0



netstandard2.1

.NET Core: •

netcoreapp1.0



netcoreapp1.1



netcoreapp2.0



netcoreapp2.1



netcoreapp2.2



netcoreapp3.0

.NET Framework: •

net11



net20

Chapter 1





net35



net40



net403



net45



net451



net452



net46



net461



net462



net47



net471



net472



net48

Exploring .NET Core

Universal Windows Platform: •

uap (instead of uap10.0)



uap10.0 (instead of win10 or netcore50)

There are a number of deprecated TFMs that should be updated. The following are the deprecated TFMs and their replacements: •

The TFM netcoreapp is the replacement for the following deprecated TFMs: •

aspnet50



aspnetcore50



dnxcore50



dnx 15

Chapter 1









Exploring .NET Core



dnx45



dnx451



dnx452

The TFM netstandard is the replacement for the following deprecated TFMs: •

dotnet



dotnet50



dotnet51



dotnet52



dotnet53



dotnet54



dotnet55



dotnet56

The TFM uap10.0 is the replacement for the following deprecated TFMs: •

netcore50



win10

The TFM netcore45 is the replacement for the following deprecated TFMs: •

win



win8



winrt

The TFM netcore451 is the replacement for the following deprecated TFMs: •

16

win81

Chapter 1

Exploring .NET Core

If migrating or developing a .NET project that supports both the .NET Framework and .NET Core, use the tag (plural), instead of the tag (singular). The use of the tag is also required when using multiple versions of the same framework (that is, the .NET Framework or .NET Core) for the same project. Listing 1-2 shows the RVJ.Core.csproj sample project file using the tag for supporting netcoreapp3.0 and net48.

Listing 1-2.  Project File Supporting netcoreapp3.0 and net48 Using the Tag

                 net48;netcoreapp3.0     WinForms_Client_App     true     WinForms_Client_App.Program   

29

Chapter 1

Exploring .NET Core

As shown in Listing 1-5 and Figure 1-20, with the changes it’s possible to see the Windows Forms designer for the .NET Framework and .NET Core.

Figure 1-20.  Windows Forms designer from the .NET Framework implementation of Windows Forms Also, the .NET assemblies referenced by the project are, as set by the TFMs in the project file, the .NET Framework assemblies and not the .NET Core assemblies, as shown in Figure 1-21, Figure 1-22, and Figure 1-23.

30

Chapter 1

Exploring .NET Core

Figure 1-21.  Windows Forms Designer, Toolbox window, and controls inserted using drag and drop

Figure 1-22.  One of the .NET Framework assemblies of the Windows Forms designer

31

Chapter 1

Exploring .NET Core

Figure 1-23.  A view of the WinForms project with the .NET assemblies of the .NET Framework listed Figures 1-21, 1-22, and 1-23 are showing, per the TFMs in the project file, the .NET Framework assemblies and not the .NET Core assemblies. Anyway, you should do a careful migration when using Windows Forms source code, even when the designer is available for .NET Core 3. The best time to migrate big projects, such as CRM solutions, ERP solutions, or any other kind of complex .NET-based projects, will be when the unified .NET 5 is available.

32

Chapter 1

Exploring .NET Core

Summary Here I summarize some do’s and don’ts for using .NET Core.

Do’s •

If a project needs the functionality of specific .NET types, do use the .NET Framework, until all the functionality in your projects is available for .NET Core and .NET BCL/FCL Core.



Do be aware that the .NET Core runtime and the infrastructure components of .NET Core as a whole will be the base of all Microsoft .NET versions from now on. This cross-platform development platform is available for Microsoft Windows, Linux implementations, and Apple macOS. This opens up new opportunities for application, library, and component developers.



When working with a more high-level API for code, do consider APIs that abstract the details of more specific operating systems and indeed of low-level programming characteristics.



If you’re planning to migrate a big application such as an ERP or CRM system to .NET Core, do remember to establish business goals for the multiplatform opportunities and do not focus solely on the technical aspects in the short term.



Do wait for .NET Core 3.0 to be released to manufacturing before starting any large migration to the .NET Core platform.

33

Chapter 1

Exploring .NET Core

Don’ts

34



Don’t begin any big migration to the .NET Core platform yet. Wait for the .NET Core 3.0 final release to be available to start.



Don’t consider .NET Core until all the functionality that your projects will be using is available for .NET Core and .NET BCL/FCL Core.



Don’t create “work-arounds” for .NET type functionalities when the objective is not the use of a low-level API.



Don’t define goals based on superficial technical observations about .NET Core. Create pieces of software based on the required functionalities for applications, libraries, and components, and make objective tests.

CHAPTER 2

About Static .NET Assembly A  cronyms The following are the acronyms introduced in this chapter: •

.NET Core command-line interface (CLI)



Base Class Library (BCL)



Common Intermediate Language (CIL)



Common Object File Format (COFF)



Common Language Infrastructure (CLI)



Common Language Infrastructure Common Object File Format (CLI COFF)



Common Language Infrastructure Portable Executable/Common Object File Format (CLI PE/ COFF)



Common Language Infrastructure Portable Executable (CLI PE)

© Roger Villela 2019 R. Villela, Exploring the .NET Core 3.0 Runtime, https://doi.org/10.1007/978-1-4842-5113-3_2

35

Chapter 2

About Static .NET Assembly



Common Language Runtime (CLR)



Common Type System (CTS)



Dynamic-link library (DLL)



General availability (GA)



Graphical user interface (GUI)



Intermediate language (IL)



Instruction Set Architecture (ISA)



Microsoft Intermediate Language (MSIL)



Portable executable (PE)



Release to market (RTM)



Software development kit (SDK)



Virtual Execution System (VES)

D  evelopment Environment In this chapter, I am using a Microsoft Windows 10 Professional 64-bit (1903) environment, and all the .NET tools and non-.NET tools are installed and running on it. These tools are as follows:

36



.NET Core SDK 3.0 x64 (preview 6).



C# programming language.



Microsoft Visual Studio 2019 (GA 16.0.3) for Microsoft Windows 10. Microsoft Visual Studio 2019 is also available for Apple macOS. You can find more information about Microsoft Visual Studio 2019 at https://visualstudio.microsoft.com.

Chapter 2

About Static .NET Assembly



Visual Studio Code (1.33). Visual Studio Code is also available for Apple macOS and for certain Linux distributions. For more information about Visual Studio Code, visit https://code.visualstudio.com.



Microsoft .NET Framework SDK 4.8 (RTM) for Microsoft Windows 10. The SDK is part of the .NET Framework Dev Pack and can be downloaded from https://dotnet.microsoft.com/download.

The Static .NET Assembly In Chapter 1, you learned that .NET assemblies are meant to be a grouping technology that provides a primary logical building block for reusing types, resources, and functionalities (more specifically, reusing .NET types, .NET resources, and .NET functionalities). A .NET assembly can be static or dynamic and can be single-module or multimodule. In this chapter, I will be talking about static, single-module .NET assemblies and their fundamental characteristics. As explained in Chapter 1, throughout this book you will be developing a base library named RVJ.Core.dll with the fundamental .NET types, .NET resources, and .NET functionalities. The RVJ.Core.dll library will also teach you about key .NET Core concepts, and because of this, you will update the library as the book progresses using various implementations.

37

Chapter 2

About Static .NET Assembly

A static or dynamic .NET assembly establishes a name scope for the .NET types, as well as a deployment unit that supports the following: •

Version control



Reuse



Deployment acting as a unit



Security permissions

In a CLR Core implementation that conforms to the CLI ECMA-335 specification, everything that is part of a static or dynamic .NET assembly and that the CLR Core implementation can work with is a type or a resource; more precisely, everything is a .NET type or a .NET resource, as shown in Figure 2-1. A CLR Core implementation expects that these .NET types and .NET resources be designed and implemented to work in cooperation and as smoothly as possible with each other. Because of the expectations of cooperation and collaboration, a CLR Core implementation sees and works with each .NET assembly as a collection of .NET types and .NET resources.

RVJ.Core.dll Manifest .NET Types

.NET Resources

Figure 2-1.  Content of a static .NET assembly called RVJ.Core stored in the .NET module RVJ.Core.dll A .NET resource is basically a data stream stored inside a .NET module in an external file. The external file could be a text file, a Microsoft Office document, another binary executable, a library file, or another file with any type of trustable content. A .NET resource can be automatically managed, fully or partially, by some piece of CLR Core infrastructure, or it can be

38

Chapter 2

About Static .NET Assembly

managed programmatically by some specialized .NET types of the .NET Core BCL library set, by some specialized .NET types of the .NET Core FCL library set, or by some specialized .NET types derived directly or indirectly from some .NET type present in one of the cited .NET Core library sets. In the context of a CLR Core implementation, a .NET type is defined by or conforms to the CTS and supported by a CLR Core VES implementation, as shown by Figure 2-2. (Remember that the CTS and VES are required pieces of the CLI ECMA-335 specification and required pieces of any CLR Core implementation for any target hardware platform and host environment.) You can find more information about the VES and CTS in my book Pro .NET Framework with the Base Class Library, published by Apress (https://www.apress.com/br/book/9781484241905). A CLR Core implementaon Y creates a specialized virtual managed execuon environment for a target hardware A and soware plaorm host B.

A CLR Core implementaon T creates a specialized virtual managed execuon environment for a target hardware M and soware plaorm host N.

.NET Assembly

.NET Assembly

.NET Type

.NET Type

.NET Resource

.NET Resource

Figure 2-2.  A CLR Core implementation is based on standards and works with abstract models for their elements

39

Chapter 2

About Static .NET Assembly

You must be aware that outside of a .NET assembly, a CLR Core implementation does not recognize a .NET type or a .NET resource as defined and required by an environment of a CLR Core implementation. A static .NET assembly and a dynamic .NET assembly support the same fundamental, logical, organizational, behavioral, and structural elements of the .NET Core platform. For a static .NET assembly to be formal, it should be persisted in a file and mapped through a single .NET module, including a name and a specific extension depending on the purpose. A static .NET assembly also should have a .NET assembly manifest, which I will be talking about in the section “About the .NET Assembly Manifest.” As mentioned in Chapter 1, in a CLR Core VES implementation, there is the concept of .NET modules (and not the concept of files). Also, static and dynamic .NET assemblies can be single-module .NET assemblies or multimodule .NET assemblies. To avoid confusion, for a .NET Core environment, a file is the same as the underlying operating system platform file system. Basically, it’s one or more sets of specialized data streams with supported aggregatable specialized behaviors and attributes (or properties if you will), whose functionality is exposed, partially or in full glory, through the Microsoft Windows operating system’s purpose-oriented native APIs. These APIs can be encapsulated and have more specialized behaviors and attributes aggregated through the perspective of the host platform libraries, as you have with the .NET Core BCL and the .NET Core FCL, for example.

40

Chapter 2

About Static .NET Assembly

Note Be aware that the use of a file extension is a convention specified by each operating system and can be, and generally is, different on each operating system. In my development environment, I am using Microsoft Windows 10 Professional (64-bit), and I am using two extensions, .EXE and .DLL. The .EXE extension is used for output binary files with executable MSIL code. The .DLL extension is also used for output binary files with executable MSIL code, but the purpose of the DLL format is to be a library of reusable executable logic or resources such as string tables for text messages, icon files, image files, audio files, video files, and other files with specific types of content. The .EXE and .DLL extensions are automatically provided by a compliant CLI ECMA-335 .NET compiler, regardless of programming language and target operating system.

Metadata System Regardless of .NET type, .NET resource, or any other .NET data structure, in the CLI ECMA-335 specification you need to formally describe the fundamental data structures of a .NET assembly, .NET type, and .NET resource, as well as the environmental behaviors expected from a CLR Core implementation for doing the required work with these .NET Core elements. The formal descriptions for the logical and physical data structures, the required relations between them, and the behaviors of the implementation of these various .NET types, .NET resources, and other structural elements are possible through another required piece of a CLR Core implementation: a metadata system. The metadata system is also described by the CLI ECMA-335 specification.

41

Chapter 2

About Static .NET Assembly

Metadata System Organization The metadata system is characterized by a series of infrastructure system descriptors used for all items declared or referenced in a .NET module. Remember that the programming model of both, CLR Core implementations and CLR implementations, is fundamentally objectoriented. This means that the items represented through the metadata system are classes, and their members are accompanied by their attributes, properties, and relationships. Figure 2-3 shows an abstract, high-level perspective of the relation between a .NET assembly (static or dynamic), a CLR Core implementation, and the metadata system. A CLR Core implementation is responsible for the administration of the relation between all those components and technologies.

CLR Core VES

CTS

CLS

Metadata System .NET Type

.NET Resource

.NET Assembly ==> .NET Type A ==> .NET Type B ==> .NET Type N

==> .NET Resource A ==> .NET Resource B ==> .NET Resource N

Figure 2-3.  High-level perspective of the relation between a .NET assembly, a CLR Core implementation, and the metadata system

42

Chapter 2

About Static .NET Assembly

The data structures in the metadata system are physically organized through normalized cross-references called tables in CLI ECMA-335 jargon and not in a hierarchical tree of data structures. The CLI loader component of the CLR Core VES implementation is responsible for loading the metadata from this physical layout, indicated by the .NET module name. The CLI loader creates a logical view from these physical data structures that are stored in a data stream (usually a physical file). This logical view is composed by another specialized group of data structures, but their instances are filled in-memory as the CLI loader loads the information from the physical data structures stored in the data stream. These instances of data structures in the logical view that are filled in-memory with data values are used by the .NET types in the .NET Core BCL System.Reflection library for accessing the metadata. In fact, this technology is officially named CLI Reflection Services because of its importance and provided functionalities. In this context, here are some observations to help you understands the fundamental purpose of CLI Reflection Services: •

In a CLR Core implementation, the CLI loader imports the metadata when a .NET type is loaded at runtime.



The loading is realized on demand.



In the logical view, the data structures can be browsed by CLI Reflection Services to access the data values of the metadata.

A column (a field in CLI ECMA-335 jargon) in a table can contain a data value or a reference that is a data pointer for a row into another table with the required data value. The purpose of these data structure schemes and implementation behavior is to avoid the duplication of data fields in the metadata data structures. This means that each category of data values resides only in one table of the metadata system. For example,

43

Chapter 2

About Static .NET Assembly

if table A needs the same data value available in table B, then table A should have a column (field) that is a data pointer to table B having the required data value. As shown in Figure 2-1, every .NET type and every .NET resource should pertain to a .NET assembly, and these .NET pieces can be easily viewed using the book’s sample project called Listing_2_1. Using Microsoft Visual Studio 2019/Microsoft Visual C#, open the solution Listing_2_1.sln for the sample project Listing_2_1. csproj from the \RVJ\2019\Platforms\CLR\Code\ ExploringDotNETCoreRuntime30\Ch02\Listing_2_1\ folder and build the sample project. You get as output a static .NET assembly with the name RVJ.Core stored in the .NET module RVJ.Core.DLL in CLI PE/COFF file format. When the sample project was created, the project properties “Assembly name” and “Default namespace” were both Listing_2_1. Change both of these project properties to RVJ.Core, as shown in Figure 2-4.

Figure 2-4.  Solution Explorer showing the static .NET assembly and output of the .NET Core console project Listing_2_1

44

Chapter 2

About Static .NET Assembly

Using Microsoft Visual Studio 2019/Microsoft Visual C# is one of the ways to generate a static .NET assembly as output. When you have the .NET Core SDK or .NET Core runtime installed, you can also use the dotnet tool to create and build a solution and the respective projects. For example, open the Microsoft Windows command prompt as Administrator, or open the Microsoft Visual Studio command prompt as Administrator. In either of these command prompts, go to the folder of the solution file: \RVJ\2019\Platforms\CLR\Code\ ExploringDotNETCoreRuntime30\Ch02\Listing_2_1\. Run the dotnet clean and dotnet build commands using the syntax shown in Listing 2-1 or Listing 2-2.

Listing 2-1.  Using the Dotnet Tool for Building Projects Without Specifying the Extension of the Solution File (.sln) dotnet clean   dotnet build Listing_2_1  

Listing 2-2.  Using the Dotnet Tool for Building the Projects with Specifying the Extension of the Solution File (.sln) dotnet clean   dotnet build Listing_2_1.sln  

You should remember that the dotnet tool is part of the .NET Core CLI’s new cross-platform toolchain for developing and supporting .NET Core libraries and applications. This cross-platform toolchain is available for any target platform for which you have a .NET Core implementation. This means the .NET Core toolchain is available when you install the .NET Core runtime and is also installed as part of the NET Core SDK.

45

Chapter 2

About Static .NET Assembly

In Figure 2-5 and Figure 2-6, you can see the command prompt running with the Administrator user as well as the execution of the dotnet clean and dotnet build commands.

Figure 2-5.  Command prompt executing with the Administrator user

Figure 2-6.  The dotnet clean command After building the book’s sample project Listing_2_1 using Microsoft Visual Studio 2019/Microsoft Visual C# or the dotnet tool’s CLI, you have as output a static .NET assembly called RVJ.Core.DLL. It’s interested to note that when you’re developing a console application using the .NET Framework, the output binary file with the executable MSIL code for this same sample project will be named RVJ.Core.EXE. But when using .NET Core 3 with a console application, you will have two output binary files: 46

Chapter 2

About Static .NET Assembly

RVJ.Core.DLL with the executable MSIL code and RVJ.Core.EXE with the native code. This is a new feature introduced in .NET Core version 3 named default executable. Now, instead of using only the dotnet tool to run a .NET Core application, you can use a default executable as you do normally on Microsoft Windows, Linux, or macOS. Using the sample project RVJ. Core.EXE, enter RVJ.Core.EXE or RVJ.Core on the command line and press Enter. You can also double-click or single-click to run the .NET Core application. The default use of double-click or single-click for running the .NET Core application depends on the configuration of the GUI environment in use. As mentioned, the default executable feature has been available since .NET Core version 3; it runs on any supported target operating system such as Microsoft Windows, Linux distributions, and macOS, for example. Figure 2-7 shows the two output binary folders named bin\debug\ netcore2.2 and bin\debug\netcore3.0 of the sample project built for .NET Core 2.2 and .NET Core 3.0, respectively.

Figure 2-7.  Configuration on Application tab for a console application and .NET Core 3.0 47

Chapter 2

About Static .NET Assembly

Figure 2-8 shows the folder bin\debug\netcore3.0 expanded and the binary files RVJ.Core.EXE, which is the native default executable, and RVJ. Core.DLL, which is the static .NET assembly with the MSIL executable code.

Figure 2-8.  Native default executable RVJ.Core.EXE and static .NET assembly RVJ.Core.DLL for .NET Core 3.0 Figure 2-9 shows the bin\debug\netcore2.2 example with the output binary file that is the static .NET assembly RVJ.Core.DLL with the MSIL executable code.

48

Chapter 2

About Static .NET Assembly

Figure 2-9.  The static .NET assembly RVJ.Core.DLL for .NET Core 2.2, which does not have the support for the default executable feature Now, using the ILDASM tool, you will try to open the files RVJ.Core. EXE and RVJ.Core.DLL in .NET Core 3.0 and see what happen. Open as Administrator a configured Microsoft Visual Studio command prompt, as shown in Figure 2-10.

49

Chapter 2

About Static .NET Assembly

Figure 2-10.  Microsoft Visual Studio 2019 shortcuts for the configured command prompt (viewed in Cortana on Microsoft Windows 10 1903) On a typical Microsoft Windows 10 configuration, the text is caseinsensitive at the command prompt; the text as it appears here is just to make it easier to read. At the Microsoft Visual Studio 2019 Developer command prompt, you will see a listing of the files in the output binary folder of the project built with .NET Core 3.0. Go to the output bin folder and write the command shown in Listing 2-3. Figure 2-11 shows the ILDASM tool command and the name of the native binary RVJ.Core.EXE as the argument value.

50

Chapter 2

About Static .NET Assembly

Listing 2-3.  Command to Execute ILDASM Tool and Open the RVJ.Core.EXE Native Executable (Non-.NET Assembly). ILDASM RVJ.Core.EXE

Figure 2-11.  Listing of files in the output binary folder for the project Listing_2_1 built with .NET Core 3.0 In Figure 2-12, you can see the message that appears stating that the RVJ.Core.EXE file does not have a CLR header data structure. Because of the absence of the CLR header data struct, the file cannot be recognized as a file in the expected CLI PE/COFF file format. A CLR header is a required formal data structure for a PE/COFF to be recognized as a valid CLI PE/ COFF.

Figure 2-12.  Message informing you that RVJ.Core.EXE is not recognized as a static .NET assembly 51

Chapter 2

About Static .NET Assembly

Listing 2-4 shows the use of ILDASM but with the name of the static .NET assembly RVJ.Core.DLL specified as the argument value.

Listing 2-4.  Command to Execute ILDASM Tool and Open the Static .NET Assembly RVJ.Core.dll ILDASM RVJ.Core.dll This time, the file opens correctly because it is a valid static .NET assembly. Now you will need to open the .NET module named RVJ.Core. DLL and see the stored .NET elements. After performing the command for the ILDASM tool, the main window of the ILDASM tool is shown with basic metadata information available, as shown in Figure 2-13. With the .NET module RVJ.Core.DLL selected, you can read the text .assembly RVJ.Core at the bottom of the main window of the ILDASM tool. This means the .NET module RVJ.Core.DLL and all shown items pertain to the .NET assembly RVJ.Core.

52

Chapter 2

About Static .NET Assembly

Figure 2-13.  ILDASM tool’s main window Double-click the Manifest item, in uppercase, just below the RVJ.Core. DLL item. After opening the manifest of the static .NET assembly RVJ.Core.DLL, you can see the content of the manifest, as shown Figure 2-14.

53

Chapter 2

About Static .NET Assembly

Figure 2-14.  Content of the RVJ.Core static .NET assembly manifest In the next section, “About the .NET assembly Manifest,” I will talk about another fundamental data structure that describes what is inside a static .NET assembly. This critical data structure is the manifest.

About the .NET Assembly Manifest Figure 2-14 shows the content of one of the fundamental items in the physical and logical organizational structure of a static .NET assembly: the manifest. The .NET assembly manifest describes what comprises a static .NET assembly. In the first lines of a .NET assembly manifest, you can see on which other .NET assemblies your RVJ.Core.DLL CLI PE/ COFF static .NET assembly depends. Listing 2-5 shows these first lines of MSIL expressing metadata. You must be aware that, as explained earlier, all the source code used in this book that is expressing metadata is written in the MSIL language implementation. Remember, the ECMA335 documentation specifies an IL and a common ISA set named CIL for the virtual computer implemented and supported by the VES, which is a 54

Chapter 2

About Static .NET Assembly

required piece of any conforming CLR Core implementation. MSIL is the implementation of this CIL ISA set, but with possible extensions of the implementer when applicable.

Listing 2-5.  Metadata of the .NET Assembly RVJ.Core, a DLL in CLI PE/COFF File Format // Metadata version: v4.0.30319 .assembly extern System.Runtime {   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A   .ver 4:2:1:0 } .assembly extern System.Runtime.Extensions {   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A   .ver 4:2:1:0 } .assembly extern System.Collections {   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A   .ver 4:1:1:0 } .assembly extern System.Console {   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A   .ver 4:1:1:0 } .assembly extern System.Diagnostics.Debug {   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A   .ver 4:1:1:0 }

)

)

)

)

)

55

Chapter 2

About Static .NET Assembly

.assembly extern System.Diagnostics.Debug {   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) .ver 4:1:1:0 } .assembly extern System.Threading {   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )   .ver 4:1:1:0 } In Listing 2-5 you can see that the .assembly MSIL directive is used with the extern MSIL keyword. This combination indicates the RVJ.Core. DLL CLI PE/COFF is a static .NET assembly using the .NET types and .NET resources available in these other .NET assemblies. Highlighted is the version number of each of the .NET assemblies referenced by the static .NET assembly RVJ.Core. Explicitly specifying the version number for a static or dynamic .NET assembly is required. The CIL ISA set of the CLI ECMA-335 offers the .ver directive for specifying the version number via metadata, and the MSIL supports the .ver directive, as shown in Listing 2-6 in the source code expressing the metadata of the example.

Listing 2-6.  Metadata Showing Details of the Static .NET Assembly RVJ.Core and the RVJ.Core.DLL File in the CLI PE/COFF File Format .assembly RVJ.Core {   .custom instance void [System.Runtime]System.Runtime. CompilerServices.CompilationRelaxationsAttribute:: .ctor(int32) = ( 01 00 08 00 00 00 00 00 )

56

Chapter 2

About Static .NET Assembly

  .custom instance void [System.Runtime]System.Runtime. CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T.. WrapNonEx    63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )        // ceptionThrows.   // --- The following custom attribute is added automatically, do not uncomment ------  //  .custom instance void [System.Runtime]System.Diagnostics. DebuggableAttribute::.ctor(valuetype [System.Runtime]System. Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )   .custom instance void [System.Runtime]System.Runtime. Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56   // ....NETCoreApp,V                 65 72 73 69 6F 6E 3D 76 33 2E 30 01 00 54 0E 14    // ersion=v3.0..T..                46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79    // FrameworkDisplay                4E 61 6D 65 00 )                                   // Name.   .custom instance void [System.Runtime]System.Reflection. AssemblyCompanyAttribute::.ctor(string) = ( 01 00 08 52 56 4A 2E 43 6F 72 65 00 00 )          // ...RVJ.Core..   .custom instance void [System.Runtime]System.Reflection. AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 05 44 65 62 75 67 00 00 )                   // ...Debug..

57

Chapter 2

About Static .NET Assembly

  .custom instance void [System.Runtime]System.Reflection. AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 )             // ...1.0.0.0..   .custom instance void [System.Runtime]System.Reflection. AssemblyInformationalVersionAttribute::.ctor(string) = ( 01 00 05 31 2E 30 2E 30 00 00 )                   // ...1.0.0..   .custom instance void [System.Runtime]System.Reflection. AssemblyProductAttribute::.ctor(string) = ( 01 00 08 52 56 4A 2E 43 6F 72 65 00 00 )          // ...RVJ.Core..   .custom instance void [System.Runtime]System.Reflection. AssemblyTitleAttribute::.ctor(string) = ( 01 00 08 52 56 4A 2E 43 6F 72 65 00 00 )          // ...RVJ.Core..   .permissionset reqmin              = {[System.Runtime.Extensions]System.Security. Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}}   .hash algorithm 0x00008004   .ver 1:0:0:0 } .module RVJ.Core.dll // MVID: {AE82D277-CE89-4942-A86A-D955B5961B3C} .custom instance void [System.Runtime]System.Security. UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003       // WINDOWS_CUI .corflags 0x00000001    //  ILONLY

58

Chapter 2

About Static .NET Assembly

In Listing 2-6 you can see more metadata content from the static .NET assembly RVJ.Core manifest, and in that metadata content I have highlighted the version number of the example RVJ.Core static .NET assembly. In the sample project, it is specified with 1.0.0.0 (Major.Minor. Build.Revision) and emitted in the metadata through the .ver directive, but you can use a sequence such as 4.6.50.2. The version number is not a figurative value; structural and behavioral concepts and data structures, including .NET types, were created for specific versions, and the implementation for fulfilling the version requirements is also part of the metadata system and therefore supported by the MSIL via this syntax. For example, the version number of a static .NET assembly is required and should be captured at compile time and used as part of the information on the references made by other static .NET assemblies to the static .NET assembly. For example, in Listing 2-5, for all static .NET assemblies that are referenced, the version number was included in the metadata of the static .NET assembly manifest. When programming with the .NET BCL Core library, you will be using the System.Version .NET type, which is the root (not inheritable, is sealed) type for any other .NET type version created for working with version numbers. Currently, System.Version is available on the System. Runtime.dll, mscorlib.dll, and netstandard.dll static .NET assemblies. The System.Version .NET type has the instance properties Build (32-bit), Major (32-bit), MajorRevision (16-bit), Minor (32-bit), MinorRevision (16-bit), and Revision (32-bit), as well as MajorRevision and MinorRevision that have System.Int16 as the base .NET type. Both instance properties MajorRevision (16-bit) and MinorRevision (16bit) are based on the 32-bit value of the Revision instance property, the MajorRevision uses the high 16 bits of the Revision instance property, and MinorRevision uses the low 16 bits of the Revision instance property.

59

Chapter 2

About Static .NET Assembly

You must supply the Major and Minor numbers because they are required, but you do not need to set the Build and Revision numbers because they are optional. Well, this is not really optional, because there is a requirement described by ECMA-335 that any compliant .NET compiler, independent of programming language, should automatically set the values for the Build and Revision instance properties to 0 if no explicit values are provided. Listing 2-7 shows a typical sequence that should be used.

Listing 2-7.  Version Number Parts Major.Minor[.Build.Revision] You must be aware that the build number is required if the revision number is provided. That is, if the revision number has been explicitly set to a value different than 0, the build number should be explicitly set to a value different than 0. A negative integer (32-bit) value is not valid for any of the six parts of a static or dynamic .NET assembly version number. For example, using Microsoft Visual Studio 2019/Microsoft Visual C#, open the solution Listing_2_1, open the Project Properties window, click the Package tab, change any version number part to a negative value, and try to save or compile the project. Figure 2-15 and Figure 2-16 show this process.

60

Chapter 2

About Static .NET Assembly

Figure 2-15.  Package tab with assembly version and assembly file version numbers

Figure 2-16.  Details of message error presented when changing any part of the version number to a negative number For a .NET Core implementation and for a .NET Framework implementation, Table 2-1 shows the recommendations for each part of the version number.

61

62

Description

This scenario means that these .NET assemblies are not made to be interchangeable. Updating the major (32-bit) version number of a .NET assembly means a considerable upgrade/update of the product and backward compatibility was not a priority and cannot be assumed by the consumer of the .NET assembly.

• RVJ.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null

• RVJ.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

For the major (32-bit) version Two or more .NET assemblies with the same assembly name can have different major number (32-bit) version numbers.

Version Number Part

Table 2-1.  Recommendations for the Use of Major, Minor, and Build Version Numbers

Chapter 2 About Static .NET Assembly

Description

A different minor (32-bit) version number for the same major (32-bit) version number of .NET assemblies with the same assembly name means that they are made to have backward compatibility as part of the priorities. This kind of update means a significant improvement in the product, such as algorithm enhancements and the inclusion of features through new APIs. Updating a .NET assembly at this level means a considerable number of improvements on the product, but the backward compatibility is a priority and can be assumed by the consumer of the .NET assembly or .NET assemblies. You can see this use of minor version number with the level of improvements in the .NET Core from 2.0 for 2.2 and in the .NET Framework from version 4.7 to 4.8, for example. (continued)

• RVJ.Core, Version=1.4.0.0, Culture=neutral, PublicKeyToken=null

• RVJ.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

For the minor (32-bit) version Two or more .NET assemblies with the same assembly name and the same major version number number can have different minor (32-bit) version numbers.

Version Number Part

Chapter 2 About Static .NET Assembly

63

64

A revision number is used in a hotfix of distinct types, such as fixes in algorithms, security fixes, and fixes required by a third-party library used by the application. Assemblies keep the same name and major and minor numbers and are fully interchangeable with the prior version that was fixed. Examples are the following:

For the revision (32-­bit) version number

• RVJ.Core, Version=1.5.101.1, Culture=neutral, PublicKeyToken=null

• RVJ.Core, Version=1.5.100.0, Culture=neutral, PublicKeyToken=null

Description

Version Number Part

Table 2-1.  (continued)

Chapter 2 About Static .NET Assembly

Chapter 2

About Static .NET Assembly

The concept of version is orthogonal in a CLR Core implementation and not specific to a context, such a specific .NET type. The version number is also used as part of another required conceptual, structural, and behavioral element of the CLI ECMA-335 specification, the assembly name. A static or dynamic .NET assembly must be uniquely identified, and a group of information is required to form this unique identification. This group of information is composed of the following: •

A simple name



A number for the version



A cryptographic key pair



Culture(s) supported by the .NET assembly

In the sequence of the content of the .NET assembly manifest, another item is highlighted in Listing 2-6: the .module MSIL directive that explicitly informs in which file the metadata information about the static .NET assembly is stored. In the CLI ECMA-335 specification, every MSIL file is a .NET module and is referenced by a logical name specified in the metadata, not the file name. Internally, a CLR Core implementation or a CLR implementation keeps various data structures for these fundamental CLR pieces and other elements. These fundamental data structures are called tables in CLI ECMA-335 jargon, and one of these tables is the Module table. The Module table is defined in CLI ECMA-335 as follows: •

Generation field: This is a two-byte value that must be 0. This field is defined as reserved.



Name field: This is the name you see in the module definition in the MSIL. As shown in Listing 2-6, in the MSIL of the static .NET assembly manifest, the value for this field is RVJ.Core.DLL.

65

Chapter 2



About Static .NET Assembly

Mvid field: This is a globally unique identifier (GUID) that identifies each module and allows you to distinguish between two versions of the same .NET module. This value should be at least 16 bytes in size and is typically displayed using hexadecimal encoding. Every time you build a static .NET module, a new and unique GUID will be generated. In Listing 2-6 the value appears in the comment // MVID: {AE82D277-CE894942-A86A-D955B5961B3C}.

Currently, as specified by CLI ECMA-335, a CLR Core VES implementation or a CLR VES implementation does not have a specified use for the MVID value. But tools such as debuggers use the MVID value of this field for uniquely identifying the .NET module. If you need to get this MVID value when using the programming library .NET Core BCL and an instance of System.Reflection.Assembly, you should use the assembly instance property System.Reflection.Assembly.ManifestModule of the System.Reflection.Module .NET type. An instance of System. Reflection.Module has the instance property System.Reflection. Module.ModuleVersionId of System.Guid .NET type. The value returned through the System.Reflection.Module.ModuleVersionId instance property is the value stored in the Mvid field.

66



EncId field: The purpose of this field is as an index into the GUID heap. This field is defined as reserved, and the value should be 0.



EncBaseId field: The purpose of this field is as an index into the GUID heap. This field is defined as reserved, and the value should be 0.

Chapter 2

About Static .NET Assembly

When coding in MSIL and using a Microsoft implementation of the CLR for .NET Core and for the .NET Framework as the target managed virtual execution environment, the .module directive can be explicitly omitted. The ILASM compiler will automatically add the .module directive and set the module name to a file name, including the appropriate extension such as .DLL or .EXE. Note that all the letters in the file extension will be in uppercase. For example, if the source code file is named Example00.msil and the target file is an executable, the name will be Example00.EXE. If the target file is a dynamic library, the name will be Example00.DLL. The behavior that changes the file extension letters to uppercase is not applied for the most recent version of .NET Core (including .NET Core preview 5). The other aspects continue to work because it is a requirement for any CIL implementation to conform to ECMA-335. For now, the sample project Listing_2_1 is organized into the following C# source code files: •

RVJ.Core.IAssemblyInfoView.cs



RVJ.Core.AssemblyInfoView.cs



RVJ.Core.IModuleInfoView.cs



RVJ.Core.ModuleInfoView.cs



Program.cs

The entry point is in the Program.cs C# source code file. As shown in Listing 2-12, the content in the Main method uses the RVJ.Core. AssemblyInfoView .NET type that has the RVJ.Core.AssemblyInfoView. DoAsync() method, which is a public method to the implementation that in fact browses the metadata using reflection. Getting information from

67

Chapter 2

About Static .NET Assembly

metadata in any kind of system requires dedicated types and methods, not only because of performance but because of aspects such as concurrency. In the RVJ.Core library, you also have .NET types for the concept of views for .NET types, which is aggregate information to be used by visual controls and nonvisual controls and in the implementation of the logic of an application. You can also see in RVJ.Core.IModuleInfoView the System. IEquatable interface, which provides support for .NET Generics. This interface equality will be implemented in other .NET types, and other functionality for comparisons and sorting will be implemented throughout this book. For example, System.IComparable, System.IComparable, System.Collections.IComparer, and System.Collections.Generic. IComparer are used by the sorting algorithms of System.Array. Comparison algorithms of the fundamental .NET types include System. Int32, System.UInt32, System.String, and System.Char to cite examples of frequently used .NET types. Listing 2-8 shows the RVJ.Core.IAssemblyInfoView interface that is responsible for aggregating the information about the .NET assembly, such as CodeBase, ImageRuntimeVersion, if published in Global Assembly Cache (GAC), and so on.

Listing 2-8.  Code of RVJ.Core.IAssemblyInfoView.cs for Declaration of RVJ.Core.IAssemblyInfoView Interface using System; using System.Reflection; namespace RVJ.Core {     public interface IAssemblyInfoView {         void DoAsync( TypeInfo typeInfo );         String FullName {             get; 68

Chapter 2

About Static .NET Assembly

        }         String FullName {             get;         }         String EntryPointMethodName {             get;         }         String Location {             get;         }         bool IsPublishedOnGAC {             get;         }     }; }; Listing 2-9 shows the RVJ.Core.IModuleInfoView interface that is responsible for aggregating the information about a .NET module, such as ImageFileMachineName, the assembly that is related, if it is a manifest module, and so on.

Listing 2-9.  Code of RVJ.Core.IModuleInfoView.cs for Declaration of RVJ.Core.IModuleInfoView Interface using System; using System.Reflection; namespace RVJ.Core {     public interface IModuleInfoView : IEquatable {

69

Chapter 2

About Static .NET Assembly

        Assembly Assembly {             get;         }        String ImageFileMachineName {             get;         }         String Name {             get;         }         String FullyQualifiedName {             get;         }         String MetadataVersion {             get;         }         String PEKindName {             get;         }         String VersionID {             get;         }         Guid VersionIDAsGUID {             get;         }         Module ManifestModule {             get; set;         }

70

Chapter 2

About Static .NET Assembly

        Boolean IsManifestModule {             get;         }         #endregion     }; }; Listing 2-10 shows the RVJ.Core.AssemblyInfoView interface that is responsible for aggregating the information about the metadata of a .NET assembly, such as ImageFileMachineName, any assembly that is related, if it is a manifest module, and so on. The RVJ.Core.AssemblyInfoView. DoAsync() method begins the async process.

Listing 2-10.  Code of RVJ.Core.AssemblyInfoView.cs for Implementation of RVJ.Core.IAssemblyInfoView Interface using using using using using using

System; System.Collections.Generic; System.Reflection; System.Text; System.Threading; System.Threading.Tasks;

namespace RVJ.Core {     public class AssemblyInfoView : System.Object, IAssemblyInfoView {        ...         private readonly List _definedTypes;         private readonly List _modules;         private readonly List _ moduleInfoViewCollection;

71

Chapter 2

About Static .NET Assembly

        private void _FillWithInfoOfDefinedTypesForAsync( Assembly assembly ) {         {                 Boolean isLocked = new Boolean();                 using ( IEnumerator definedTypes = assembly.DefinedTypes.GetEnumerator() ) {                     Monitor.Enter( definedTypes, ref isLocked );                     if ( isLocked ) {                         try {                             this._definedTypes.AddRange( assembly.DefinedTypes );                         } finally {                             Monitor.Exit( definedTypes );                         };                     };                 };             };             return;         }         private void _FillWithAssemblyInfoForAsync( TypeInfo type ) {             if ( type == null )                 type = IntrospectionExtensions.GetTypeInfo( typeof( RVJ.Core.AssemblyInfoView ) );

72

Chapter 2

About Static .NET Assembly

            this._assembly = IntrospectionExtensions. GetTypeInfo( type ).Assembly;             this._assemblyName = this._assembly.GetName();             this._version = this._assemblyName.Version;             this._manifestModule = this._assembly.ManifestModule;             PortableExecutableKinds PEKinds;             ImageFileMachine imageFileMachine;             this._manifestModule.GetPEKind( out PEKinds, out imageFileMachine );             this._PEKindName = PEKinds.ToString();             this._imageFileMachineName = imageFileMachine. ToString();             this._FillWithInfoOfDefinedTypesForAsync( this._ assembly );             this._manifestModule = this._assembly.ManifestModule;             this._modules.AddRange( this._assembly.GetModules() );             foreach ( Module module in this._modules ) {                  IModuleInfoView item = new ModuleInfoView( module );                 this._moduleInfoViewCollection.Add( item );             }             return;         } public AssemblyInfoView() {     this._buffer = new StringBuilder();     this._definedTypes = new List(); 73

Chapter 2

About Static .NET Assembly

    this._modules = new List();     this._moduleInfoViewCollection = new List();     return; } public void DoAsync( TypeInfo typeInfo ) {     Action action = ( Object type ) => { this._ FillWithAssemblyInfoForAsync( ( TypeInfo ) type ); };     using ( Task taskGetAssemblyInfo = new Task( action, typeInfo ) ) {         taskGetAssemblyInfo.Start();         taskGetAssemblyInfo.Wait();     };     return; } public override String ToString() {        String local;     this._buffer.AppendFormat( "Assembly Full Name (display name): {0}\nAssembly location: {1}\nAssembly Code base: {2}\ nCLR version: {3}\nAssembly is static: {4}\nAssembly full name (via ToString()): {5}\nName: {6}\nCultureName: {7}\ nVersion.Major: {8}\nVersion.Minor: {9}\nVersion.Build: {10}\nVersion.Revision: {11}\nVersion.MajorRevision: {12}\ nVersion.MinorRevision: {13}\nPublic key token: {14}\ nManifest Module: {15}\n", this.FullName, this.Location, this.CodeBase, this.ImageRuntimeVersion, this.IsDynamic. 74

Chapter 2

About Static .NET Assembly

ToString(), this.FullName, this.Name, this.CultureName, this.VersionMajorNumber, this.VersionMinorNumber, this. VersionBuildNumber, this.VersionRevisionNumber, this. VersionMajorRevisionNumber, this.VersionMinorRevisionNumber, this.PublicKeyTokenValue, this.ManifestModuleName );        local = String.Intern( this._buffer.ToString() );     this._buffer.Clear();     return local; } }; }; Listing 2-11 shows the RVJ.Core.ModuleInfoView interface that is responsible for aggregating the information about the metadata of a .NET module.

Listing 2-11.  Code of RVJ.Core.ModuleInfoView.cs for Implementation of IModuleInfoView Interface using System; using System.Reflection; namespace RVJ.Core {     public class ModuleInfoView : System.Object, IModuleInfoView {         public ModuleInfoView( Module moduleSource ) {             if ( moduleSource != null ) {                 this._FillWithModuleInfo( moduleSource );             };

75

Chapter 2

About Static .NET Assembly

            return;         }         private void _FillWithModuleInfo( Module module ) {             this._assembly = module.Assembly;             this._name = module.Name;             this._fullyQualifiedName = module.FullyQualifiedName;             this._versionIDAsGUID = module.ModuleVersionId;             this._versionID = this._versionIDAsGUID.ToString();             this._metadataVersion = module.MDStreamVersion. ToString();             PortableExecutableKinds PEKinds;             ImageFileMachine imageFileMachine;             module.GetPEKind( out PEKinds, out imageFileMachine );             this._PEKindName = PEKinds.ToString();             this._imageFileMachineName = imageFileMachine. ToString();             if ( this._versionIDAsGUID == this._assembly. ManifestModule.ModuleVersionId )                 this._manifestModule = module;             return;         }         public override String ToString() {             return this._fullyQualifiedName;         }

76

Chapter 2

About Static .NET Assembly

        public override Boolean Equals( Object obj ) {             return ( Object.ReferenceEquals( this, obj ) || this.Equals( ( obj as IModuleInfoView ) ) );         }         Boolean IEquatable.Equals( IModuleInfoView other ) {              return ( ( other != null ) && ( this. VersionIDAsGUID.Equals( other.VersionIDAsGUID ) ) );         }         public static Boolean operator ==( ModuleInfoView first, ModuleInfoView other ) {             return ( ( first != null ) && first.Equals( other ) );         }         public static Boolean operator !=( ModuleInfoView first, ModuleInfoView other ) {             return !( ( first != null ) && ( first == other ) );         }     }; }; Listing 2-12 shows the main implementation for AssemblyInfoView and ModuleInfoView.

77

Chapter 2

About Static .NET Assembly

Listing 2-12.  Source code that use the implementations of AssemblyInfoView.cs and ModuleInfoView.cs using using using using

System; System.Collections.Generic; System.Threading.Tasks; System.Reflection;

namespace RVJ.Core {     public class Program {         static void Main() {             AssemblyInfoView asmInfo = new AssemblyInfoView();             using ( Task getAssemblyInfo = Task.Run( () => { asmInfo.DoAsync( ( ( TypeInfo ) typeof ( STAThreadAttribute ) ) ); return; } ) ) {                 getAssemblyInfo.Wait();             };             TypeInfo[] definedTypes = asmInfo.DefinedTypes;             Console.WriteLine( "{0}\nTotal of defined types for assembly {1} is {2}.\n", asmInfo.ToString(), asmInfo.Name, definedTypes.Length.ToString() );             asmInfo = null;             Console.WriteLine( "Press to finish" );             Console.ReadLine();             return;         }     }; }; 78

Chapter 2

About Static .NET Assembly

Summary Here are some do’s and don’ts about using static .NET assemblies.

Do’s •

Do use a scenario-based approach to explain for developers the requirements for .NET assembly names and .NET module names. Examples of typical scenarios that influence the .NET assembly names are the local culture, accessibility, diversity, target market, development environment culture, technical aspects such as operating system security restrictions, and other human, business, or environment factors.



Do always use more meaningful names for .NET assemblies, if possible, which means the following: •

Do use names that are more abstractly aligned with the purpose of .NET types, .NET resources, and .NET functionalities implemented in the .NET assemblies. An application with an intermediate or advanced level of complexity has many contextual acronyms, and when these acronyms are used with the names, it is critical to have documentation for those contextual acronyms and to update the documentation within a timeline that follows the specific context that the acronyms came from, such as the financial market, security market, or whatever else it takes. Examples include Financial. Core, Financial.Core.DLL, Financial.Standards, and Financial.Standards.DLL for .NET types, .NET resources, and functionalities representing 79

Chapter 2

About Static .NET Assembly

infrastructural and much more orthogonal concepts and functionalities of the financial market. These names can be used and reused through other .NET applications, reducing the cost of development and preserving the contextual knowledge.

80



Do use names that are more concretely aligned with the purpose of .NET types, .NET resources, and .NET functionalities implemented on the .NET assemblies. Every context has at least one logical section that groups data structure. A logical section is what you know as a “namespace” in .NET, and a data struct is what you know as a .NET type. A context represents the knowledge of a specific area and its subareas. Continue using the financial market example, you could have Financial.Investments, Financial.Investments.DLL, and Financial. Institutions, and Financial.Institutions.DLL.



Do remember that the name for the .NET module should be as short but easy to remember as possible, such as RVJ.Core.DLL.



Do explore hypothetical, more complex scenarios for versioning and numbering expected for the near future of an application. Certain scenarios may not be available currently, but the evolution of the application might require more complex project policies for versioning and numbering. When possible, start exploring more advanced scenarios using the four digits Major.Minor.Build.Release. This will help you understand the application’s evolution and start planning for the time, financial cost, culture adaptation, expected results, required goals, and other technical and cultural goals.

Chapter 2

About Static .NET Assembly

Don’ts •

Except when explicitly defined by a project directive for a specific scenario, don’t use names based on acronyms that are outside the core business context of the .NET types, .NET resources, and .NET functionalities. For example, it is common that names for .NET modules are created specifically to interact with native APIs, so use names that are more closely related to native APIs than names that are more related to the core business area of the application. In general, these scenarios are using the P/Invoke platform.



Don’t use a complex scheme for versioning and numbering. Start defining project policies for major and minor numbers and apply them to the development lifecycle and for predictable scenarios expected in the deployment environments. After an established period of evaluation for these contexts, create documentation explaining what should be done and the adequate motivations for adopting the conventions.



Don’t use numbers as the name or part of the name of.NET modules, assemblies, namespaces, or .NET types and their members. This doesn’t apply when the name is a standard, a law, or a norm, and when it is a clear and uniform choice. For example, Unicode is an international standard and has names such as UTF-8, UTF-16, and UTF-32 that are used as the name or as part of the name of certain .NET types, but they are used as UTF8, UTF16, and UTF32, without the hyphen. These numbers mean 8-bit, 16-bit, and 32-bit sizes and are defined by the Unicode standard. This is also reinforced in the implementations of the .NET types. 81

CHAPTER 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types As you know, the .NET platform supports two types of .NET assemblies, static and dynamic. This chapter and the next are about working with dynamic .NET assemblies; it covers the purpose and benefits of dynamic .NET assemblies and how to perform fundamental tasks with them. Also, you will learn how to define a dynamic .NET assembly, a dynamic .NET module, a dynamic .NET reference type, and a dynamic .NET field member.

About Dynamic Assemblies Dynamic assemblies are created dynamically at runtime and by using a specialized API of the .NET Core BCL called the Reflection Emit API, which is part of Reflection Services. A dynamic .NET assembly is created and executed directly in memory. But a dynamic assembly can be saved in a storage device.

© Roger Villela 2019 R. Villela, Exploring the .NET Core 3.0 Runtime, https://doi.org/10.1007/978-1-4842-5113-3_3

83

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Within .NET Core 3 preview 6, the version of Reflection Services implemented with the .NET Core BCL does not yet have the functionality to persist a dynamic .NET assembly and turn it into a static .NET assembly. This functionality is expected to be available in the final implementation of .NET 3, slated for the second half of 2019. Also, Microsoft has announced that there will be one runtime for .NET 5 that will be based on the .NET Core runtime and will be available as part of the .NET Core BCL. You can read more about .NET 5 at https://devblogs.microsoft.com/dotnet/introducing-­ net-­5/. Compilers and code generation tools typically use the functionality of the System.Reflection.Emit namespace. The System.Reflection.Emit namespace provides the capabilities you need to create a dynamic .NET assembly and other dynamic .NET elements such as dynamic .NET modules, dynamic .NET types, dynamic .NET fields, dynamic .NET properties, dynamic .NET methods, dynamic .NET events, dynamic .NET interfaces, and others. In fact, the System. Reflection.Emit namespace has the capability to emit metadata and the MSIL required for creating CLR Core–supported class types, struct types, interface types, and their members, such as fields, properties, and methods, for example. With the functionality provided by System.Reflection.Emit and Reflection Services as a whole, you can create tools specifically designed for business segments, using visual modeling tools based on information sources such as databases, artificial intelligence (AI) models, and cloud-­ based services. System.Reflection.Emit and Reflection Services are powerful tools when applied on research and academic projects too, such as aerospace,

84

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

environmental, and agriculture. In fact, System.Reflection.Emit and Reflection Services as a whole can be applied to any segment for which software can be developed. In the folder \Projects\RVJ\2019\Platforms\CLR\ Code\ExploringDotNETCoreRuntime30\Ch03\Listing_3_1\ there are two folders; the Listing_3_1 folder holds the console client, and the RVJ.Core folder has the project library that makes use of the System.Reflection. Emit .NET types for this book’s examples. Open the solution \Projects\RVJ\2019\ Platforms\CLR\Code\ExploringDotNETCoreRuntime30\Ch03\ Listing_3_1\Listing_3_1.sln. The sample code in Listing 3-1 shows how to define a dynamic assembly with one module, as every .NET assembly must have a module, as you learned from Chapter 1. Specifically, Listing 3-1 shows the client code of Listing_3_1.csproj that uses the RVJ.Core.dll library to encapsulate functionalities of System.Reflection.Emit with a focus on productivity. I am using the RVJ.Core.dll encapsulated functionalities to define a dynamic type named RVJ.Core.Person with three private dynamic fields to hold the ID, name, and age of a person. RVJ.Core.DynamicBuilder is a .NET class type that implements the RVJ.Core.IDynamicBuilder .NET interface type. The type declares a method called RVJ.Core.IDynamicBuilder. DefineDynamicAssembly, which is responsible for encapsulating System. Reflection.Emit.AssemblyBuilder functionality for defining a dynamic .NET assembly. The RVJ.Core.IDynamicBuilder.DefineDynamicAssembly method returns an instance of the RVJ.Core.DynamicAssembly .NET class type. After defining a dynamic .NET assembly, you should use the instance of the dynamic .NET assembly and define a dynamic .NET module. The RVJ.Core.DynamicAssembly .NET class type implements the

85

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

R­ VJ.Core.IDynamicAssembly .NET interface type that has the RVJ.Core.IDynamicAssembly.DefineDynamicModule() method that returns an instance of RVJ.Core.DynamicModule. The next step is defining the RVJ.Core.Person dynamic .NET class type. For a static .NET assembly or a dynamic .NET assembly, any .NET type is defined within a .NET module and not within a .NET assembly. One of the responsibilities of a .NET assembly, static or dynamic, is to describe for a .NET CLR implementation, Core or Full, which .NET types are exported and available for use by other .NET assemblies and .NET types. You should use the instance of the dynamic .NET module to define your dynamic .NET type RVJ.Core.Person. The RVJ.Core.DynamicModule .NET class type implements the RVJ.Core.IDynamicModule .NET interface type that declares the method RVJ.Core.IDynamicModule. DefineDynamicType() that returns an instance of RVJ.Core.DynamicType. With the dynamic .NET type defined, you need to define the members, such as fields, properties, methods, events, and delegates. In this chapter, you are working with dynamic fields and the introductory aspects of working with dynamic members. Chapter 4 is focused on working with dynamic properties and dynamic methods, and Chapter 5 is focused on working with dynamic events, dynamic delegates, and dynamic interfaces because .NET delegate types, .NET event types, and .NET interface types, static or dynamic, are special .NET types for the .NET CLR. RVJ.Core.IDynamicType has the RVJ.Core.IDynamicType. DefineDynamicField instance method that returns an instance of the RVJ.Core.DynamicField .NET class type. Listing 3-1 shows that the client code is not currently using the instance of RVJ.Core.DynamicField that is returned by the method RVJ.Core.IDynamicType.DefineDynamicField, but internally the instances are part of a collection of members that will be used in the next parts of the implementation.

86

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Listing 3-1.  Client Code for the RVJ.Core Encapsulated Functionalities of System.Reflection.Emit .NET Types using System; using RVJ.Core; namespace Listing_3_1 {        public class Program {               static void Main() { String assemblyName = "RVJ.Core.Person"; IDynamicBuilder personBuilder = new DynamicBuilder(assemblyName); IDynamicAssembly personDynamicAssembly = personBuilder. DefineDynamicAssembly(); IDynamicModule personDynamicModule = personDynamicAssembly. DefineDynamicModule(); IDynamicType personDynamicType = personDynamicModule. DefineDynamicType(assemblyName, ClassTypeFlags.Public, typeof(System.Object)); personDynamicType.DefineDynamicField("_id", typeof(System. UInt32), FieldFlags.Private); personDynamicType.DefineDynamicField("_name", typeof(System. String), FieldFlags.Private); personDynamicType.DefineDynamicField("_age", typeof(System. UInt32), FieldFlags.Private); Object person = personDynamicType.CreateAnInstance("RVJ.Core. Person");

87

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Type personType = person.GetType(); FieldInfo personFieldId = personType.GetField("_id", (BindingFlags.NonPublic  | BindingFlags.Instance)); FieldInfo personFieldName = personType.GetField("_name", (BindingFlags.NonPublic  | BindingFlags.Instance)); FieldInfo personFieldAge = personType.GetField("_age", (BindingFlags.NonPublic  | BindingFlags.Instance)); UInt32 newId = (UInt32) personFieldId.GetValue(person); String newName = (String) personFieldName.GetValue(person); UInt32 newAge = (UInt32) personFieldAge.GetValue(person); if (newName == null) newName = String.Empty; Console.WriteLine("Before new values...\nperson._id: {0}\ nperson._name: {1}\nperson._age: {2}\n", newId.ToString(), newName, newAge.ToString()); newId = 100; newName = "New Name!!!"; newAge = 25; personFieldId.SetValue(person, newId); personFieldName.SetValue(person, newName); personFieldAge.SetValue(person, newAge); newId = (UInt32) personFieldId.GetValue(person); newName = (String) personFieldName.GetValue(person); newAge = (UInt32) personFieldAge.GetValue(person); Console.WriteLine("After new values assigned...\nperson._id: {0}\nperson._name: {1}\nperson._age: {2}\n", newId.ToString(), newName, newAge.ToString());

88

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Console.ReadLine(); Console.WriteLine("Press to finish...");               }        }; }; With the dynamic .NET assembly, the dynamic .NET module, the dynamic .NET class type, and the dynamic .NET field members created, you are ready to define an instance of the RVJ.Core.Person .NET class type. This is made by another method of RVJ.Core.DynamicType, the RVJ. Core.DynamicType.CreateAnInstance.

Organization of RVJ.Core .NET Types The client code shown in Listing 3-1 uses a group of .NET types defined in the RVJ.Core project. This section shows the source code for each these .NET types and explains the organization.

Purpose of .NET Types The purpose of the RVJ.Core.dll .NET types is to encapsulate the complex details of the System.Reflection.Emit .NET types with a focus on improving the productivity, as you can see in Figure 3-1.

89

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Figure 3-1.  The Solution Explorer with the RVJ.Core project and all .NET types and the client application Listing_3_1

.NET Type Interfaces There are a group of interfaces for representing the principal dynamic .NET types in common use when you are writing code using the typical software development tools for .NET development.

Builders of the System.Reflection.Emit Namespace The System.Reflection.Emit .NET types and functionalities have the concept of builders, which are responsible for the functionalities of defining and working with dynamic .NET elements.

90

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

For example, System.Reflection.Emit.AssemblyBuilder is responsible for the functionalities related to defining and working with a dynamic .NET assembly. In .NET BCL, System.Reflection.Emit.ModuleBuilder is responsible for the functionalities related to defining and working with a dynamic .NET module that should be associated with a dynamic .NET assembly. These two .NET types builders, System.Reflection.Emit. AssemblyBuilder and System.Reflection.Emit.ModuleBuilder, are encapsulated through the RVJ.Core.IDynamicBuilder .NET interface shown in Listing 3-2, the .NET interface RVJ.Core.IDynamicAssembly shown in Listing 3-4, and the .NET interface RVJ.Core.IDynamicModule shown in Listing 3-6. The RVJ.Core.IDynamicBuilder .NET interface type is implemented by the RVJ.Core.DynamicBuilder .NET class type shown in Listing 3-3. The RVJ.Core.IDynamicAssembly .NET interface type is implemented by RVJ.Core.DynamicAssembly, as shown in Listing 3-5. The RVJ.Core.IDynamicModule .NET Interface type is implemented by RVJ.Core.DynamicModule, as shown in Listing 3-7.

Listing 3-2.  Declaration of RVJ.Core.IDynamicBuilder and Members namespace RVJ.Core {        public interface IDynamicBuilder {               IDynamicAssembly DefineDynamicAssembly();        } }

91

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Listing 3-3.  Definition of RVJ.Core.DynamicBuilder Encapsulating the Functionalities of the System.Reflection.Emit.AssemblyBuilder .NET Class Type and Members using System; using System.Reflection; using System.Reflection.Emit; namespace RVJ.Core {        public class DynamicBuilder : IDynamicBuilder {               private AssemblyBuilder _assemblyBuilder;               private AssemblyName _assemblyName;               protected DynamicBuilder() : base() { }               public DynamicBuilder(String nameOfAssembly) : this() {                      this._assemblyName = new AssemblyName(nameOfAssembly);               }               public IDynamicAssembly DefineDynamicAssembly() {                      this._assemblyBuilder = AssemblyBuilder. DefineDynamicAssembly(this._assemblyName,                             AssemblyBuilderAccess. RunAndCollect);                      return new DynamicAssembly(this. _assemblyBuilder);               }               protected internal AssemblyBuilder _getAssemblyBuilder() {

92

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

                     return this._assemblyBuilder;               }        }; };

Listing 3-4.  Declaration of RVJ.Core.IDynamicAssembly and Members namespace RVJ.Core {        public interface IDynamicAssembly {               IDynamicModule DefineDynamicModule();        } }

Listing 3-5.  Definition of RVJ.Core.DynamicAssembly Encapsulating the Functionalities of the System.Reflection.Emit. AssemblyBuilder .NET Class Type and Members using System.Reflection.Emit; namespace RVJ.Core {        public class DynamicAssembly : IDynamicAssembly {               private AssemblyBuilder _assemblyBuilder;               protected DynamicAssembly(): base() { }               public DynamicAssembly(AssemblyBuilder builder) : this() {                      this._assemblyBuilder  = builder;               }

93

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

              protected internal AssemblyBuilder _getAssemblyBuilder() {                      return this._assemblyBuilder;               }               public IDynamicModule DefineDynamicModule() {                      return new DynamicModule(this._ assemblyBuilder.DefineDynamicModule(                             this._assemblyBuilder.GetName().Name),                             this._assemblyBuilder);               }        } };

Listing 3-6.  Declaration of RVJ.Core.IDynamicModule Interface and Members using System; namespace RVJ.Core {     public interface IDynamicModule {          IDynamicType DefineDynamicType(String nameOfType, ClassTypeFlags flags, Type directAncestor);        } }

94

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Listing 3-7.  Definition of RVJ.Core.DynamicModule Encapsulating the Functionalities of the System.Reflection.Emit.ModuleBuilder Class and Members using System; using System.Reflection; using System.Reflection.Emit; namespace RVJ.Core {     public class DynamicModule : IDynamicModule {         private ModuleBuilder _moduleBuilder;             private AssemblyBuilder _assemblyBuilder;             protected DynamicModule(): base() {             }             public DynamicModule(ModuleBuilder builder,                 AssemblyBuilder assembly) : this() {                      this._moduleBuilder  = builder;                      this._assemblyBuilder = assembly;             }             protected internal ModuleBuilder _ getModuleBuilder() {                 return this._moduleBuilder;             }             protected internal AssemblyBuilder _ getAssemblyBuilder() {                 return this._assemblyBuilder;             }             public IDynamicType DefineDynamicType(String nameOfType, ClassTypeFlags flags, Type directAncestor) { 95

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

                return new DynamicType(                     this._moduleBuilder.DefineType(nameOfType,                         ((TypeAttributes) flags),                         directAncestor), this._moduleBuilder);                 }         };     }; } With a dynamic .NET assembly and a dynamic .NET module encapsulated in the custom .NET types of RVJ.Core.dll, it is possible to work with a dynamic .NET type and a dynamic .NET member, which in this case is a dynamic .NET field. RVJ.Core.IDynamicType, shown in Listing 3-8, and RVJ.Core. IDynamicField, shown in Listing 3-9, are the .NET interfaces implemented by RVJ.Core.DynamicType, shown in Listing 3-10, and RVJ.Core. DynamicField, shown in Listing 3-11, and are the .NET interfaces used for encapsulating System.Reflection.Emit.TypeBuilder and System. Reflection.Emit.FieldBuilder, respectively.

Listing 3-8.  Declaration of RVJ.Core.IDynamicType Interface and Members using System; namespace RVJ.Core {        public interface IDynamicType {               IDynamicField DefineDynamicField(String fieldName, Type fieldType, FieldFlags flags);               Object CreateAnInstance(String dynamicTypeName);        } }; 96

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Listing 3-9.  Definition of RVJ.Core.DynamicType Encapsulating the Functionalities of the System.Reflection.Emit.TypeBuilder Class and Members using System; using System.Collections.Generic; using System.Reflection.Emit; namespace RVJ.Core {        public class DynamicType : IDynamicType {               private               private               private               private               private

TypeBuilder _typeBuilder; ModuleBuilder _moduleBuilder; List _dynamicFields; Boolean _isTypeCreated; Type _typeCreated;

              protected DynamicType() : base() {                      this._dynamicFields = new List();               }               public DynamicType(TypeBuilder builder, ModuleBuilder module) : this() {                      this._typeBuilder  = builder;                      this._moduleBuilder = module;               }               protected internal TypeBuilder _getTypeBuilder() {                      return this._typeBuilder;               }

97

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

              protected internal ModuleBuilder _ getModuleBuilder() {                      return this._moduleBuilder;               }               public IDynamicField DefineDynamicField(String fieldName, Type fieldType, FieldFlags flags) {                      return this.AddField(fieldName, fieldType, flags, this._typeBuilder);               }               private IDynamicField AddField(String fieldName, Type fieldType, FieldFlags flags, TypeBuilder builder) {                      IDynamicField local = new DynamicField(fieldName, fieldType, flags, builder);                      this._dynamicFields.Add(local);                      return local;               }               public Object CreateAnInstance(String dynamicTypeName) {                      if (!this._isTypeCreated) {                             this._typeCreated = this._ typeBuilder.CreateType();                             this._isTypeCreated = true;                      }                      return Activator.CreateInstance(this._ typeCreated);               }        } }; 98

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Listing 3-10.  Declaration of RVJ.Core.IDynamicField Interface namespace RVJ.Core {        public interface IDynamicField {        }; };

Listing 3-11.  Definition of RVJ.Core.DynamicField Encapsulating the Functionalities of the System.Reflection.Emit.FieldBuilder Class and Members using System; using System.Reflection; using System.Reflection.Emit; namespace RVJ.Core {        public class DynamicField : IDynamicField {               private FieldBuilder _fieldBuilder;               private TypeBuilder _typeBuilder;               protected DynamicField() : base() {                      return;               }               protected DynamicField(TypeBuilder builder) : this() {                      this._typeBuilder  = builder;                      return;               }

99

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

              protected internal DynamicField(String fieldName, Type fieldType, FieldFlags flags, TypeBuilder builder) : this(builder) {                      this._fieldBuilder = this._typeBuilder. DefineField(fieldName, fieldType, ((FieldAttributes) flags));                      return;               }               protected internal TypeBuilder _getTypeBuilder() {                      return this._typeBuilder;               }               protected internal FieldBuilder _ getFieldBuilder() {                      return this._fieldBuilder;               }        } };

The Flags and Definition of .NET Types The last two types, for now, are related to the flags used for defining .NET types. Specifically, RVJ.Core.ClassTypeFlags and RVJ.Core.FieldFlags are enums that encapsulate the enum options of System.Reflection. TypeAttributes and System.Reflection.FieldAttributes, respectively. See Listing 3-12 and Listing 3-13.

100

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Listing 3-12.  Declaration of Enum RVJ.Core.ClassTypeFlags That Encapsulates Enum Options of System.Reflection.TypeAttributes using System.Reflection; namespace RVJ.Core {        public enum ClassTypeFlags {               Private = (TypeAttributes.NotPublic | TypeAttributes.Class),               Public = (TypeAttributes.Public | TypeAttributes. Class),               Abstract = (TypeAttributes.Abstract | TypeAttributes.Class)        } };

Listing 3-13.  Declaration of Enum RVJ.Core.ClassTypeFlags That Encapsulates Enum Options of System.Reflection.TypeAttributes using System.Reflection; namespace RVJ.Core {        public enum FieldFlags {               Private = FieldAttributes.Private,               Public = FieldAttributes.Public,               Static = FieldAttributes.Static        } }; Now, I’ll discuss the details of the implementation of the RVJ.Core.NET types and the System.Reflection.Emit.NET types and behaviors that are encapsulated.

101

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

About System.Reflection.Emit Type Builders System.Reflection.Emit uses the concept of builders, which are special .NET types with most of the functionalities for defining and working with dynamic .NET types. For example, the System.Reflection.Emit. AssemblyBuilder .NET type is used for defining and representing a dynamic .NET assembly, as shown by the IntelliSense tip in Figure 3-2. But there are other builders, such as System.Reflection.Emit.ModuleBuilder and System.Reflection.Emit.TypeBuilder, and builders for the members of dynamic .NET types, such as System.Reflection.Emit. FieldBuilder, System.Reflection.Emit.PropertyBuilder, and others, also shown in Figure 3-2.

Figure 3-2.  Common group of builders

Using an Assembly Type Builder When defining a dynamic .NET assembly, you use the System. Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly() static method, which has two implementations, as shown in Figure 3-3, Figure 3-­4, and Figure 3-5. The first implementation accepts two parameters. The first parameter is the assembly name, and the second

102

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

parameter consists of flags for permissions and execution features for the dynamic .NET assembly.

Figure 3-3.  System.Reflection.Emit.AssemblyBuilder has the functionalities for defining and working with a dynamic .NET assembly.

Figure 3-4.  The System.Reflection.Emit.AssemblyBuilder. DefineDynamicAssembly static method has two implementations.

Figure 3-5.  System.Reflection.Emit.AssemblyBuilder. DefineDynamicAssembly with argument values

Naming the Dynamic .NET Assembly First, you need a name for your dynamic .NET assembly. When the dynamic .NET assembly is persisted, this is the same name used for the module name, that is, the file name. In the example, it’s RVJ.Core.DLL or RVJ.Core.EXE depending on the type of project. But you must remember that the name of the .NET assembly, dynamic or static, and the name of the .NET module are distinct entities and do not need to have the same names. As shown in Figure 3-6, the argument value for 103

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

the first parameter is an instance of System.Reflection.AssemblyName, which is responsible for information about the unique identity of a .NET assembly, dynamic or static.

Figure 3-6.  System.Reflection.AssemblyName is the .NET type responsible for information about the unique identity of a .NET assembly, static or dynamic

AssemblyBuilderAccess Enum Options As shown in Figure 3-7, the argument value System.Reflection.Emit. AssemblyBuilderAccess.RunAndCollect for the access parameter defines that the dynamic .NET assembly will be automatically unloaded and the memory will be released as soon as the GC mechanism of the .NET Core runtime identifies that the dynamic .NET assembly and objects are no longer in use. For typical dynamic .NET assemblies with just a few simple .NET types, this is a good option; however, when you are looking for more complex dynamic .NET types and better performance, you must review the scenarios for your applications and components and create specific tests of performance impact when using the System.Reflection.Emit. AssemblyBuilderAccess.RunAndCollect enum option.

Figure 3-7.  System.Reflection.Emit.AssemblyBuilderAccess. RunAndCollect defines that the dynamic .NET assembly will be automatically unloaded and all memory will be released 104

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

When using the .NET Framework, not .NET Core, the System. Reflection.Emit.AssemblyBuilderAccess enum has more options, but at the time of writing, these same options are not available for .NET Core 3 preview 6; only the System.Reflection.Emit.AssemblyBuilderAccess. Run and System.Reflection.Emit.AssemblyBuilderAccess. RunAndCollect enum options are available. Check the official Microsoft documentation for more information about these enum options; you can find it here: https://docs.microsoft.com/en-us/dotnet/api/system.reflection. emit.assemblybuilderaccess?f1url=https%3A%2F%2Fmsdn.microsoft. com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DEN-US%26k%3Dk (System.Reflection.Emit.AssemblyBuilderAccess);k(DevLang-csharp) %26rd%3Dtrue&view=netframework-­4.8 Figure 3-8 shows the other enum options of System.Reflection.Emit. AssemblyBuilderAccess when using the .NET Framework.

Figure 3-8.  Enum options of System.Reflection.Emit. AssemblyBuilderAccess when using the .NET Framework Figure 3-9 shows the other enum options of System.Reflection.Emit. AssemblyBuilderAccess when using .NET Core.

105

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Figure 3-9.  Enum options of System.Reflection.Emit. AssemblyBuilderAccess when using .NET Core As shown in Figure 3-10, with the System.Reflection.Emit. AssemblyBuilderAccess.Run enum option, you can execute the dynamic .NET assembly, but this option indicates that the dynamic .NET assembly has been created to run only in memory and cannot be persisted.

Figure 3-10.  With System.Reflection.Emit.AssemblyBuilderAccess. Run, the dynamic .NET assembly runs in memory only but cannot be persisted. After defining the dynamic .NET assembly, you need to define a dynamic transient .NET module. A dynamic .NET module is transient, does not have an extension (EXE or DLL), and cannot be saved. Figure 3-­11 shows a System.Reflection.Emit.ModuleBuilder .NET type that is returned by the System.Reflection.Emit.AssemblyBuilder. DefineDynamicModule() instance method.

Figure 3-11.  Definition of a dynamic transient .NET module

106

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

The System.Reflection.Emit.AssemblyBuilder. DefineDynamicModule() method accepts an argument value for the name of the dynamic .NET assembly. In Figure 3-12 you can see that the System. Reflection.Emit.AssemblyBuilder.GetName() instance method is used to get access to the instance of the System.Reflection.AssemblyName .NET type that was used on definition of the unique identity of the dynamic .NET assembly.

Figure 3-12.  System.Reflection.Emit.AssemblyBuilder.GetName instance method returns an instance System.Reflection. AssemblyName, used for defining the unique identity of the dynamic .NET assembly. As shown in Figure 3-13, with access to the instance of the System. Reflection.AssemblyName .NET type, the System.Reflection. AssemblyName.Name instance property is used to obtain the name of the dynamic .NET assembly, which also will be used as the name for the dynamic .NET module that you are defining.

Figure 3-13.  The System.Reflection.AssemblyName.Name instance property is used to obtain the name of the dynamic .NET assembly, which will be used for the naming the dynamic .NET module.

107

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Defining a Dynamic .NET Type Now that you have a dynamic .NET assembly, you need a dynamic .NET type. For the definition of a dynamic .NET type, you can use the instance of the System.Reflection.Emit.ModuleBuilder class and the ­System. Reflection.Emit.ModuleBuilder.DefineType instance method that returns an instance of the System.Reflection.Emit.TypeBuilder .NET type. The System.Reflection.Emit.ModuleBuilder.DefineType instance method has seven implementations, and here I am using the first signature number, as shown in Figure 3-14.

Figure 3-14.  Definition of a dynamic .NET type using the instance method System.Reflection.Emit.ModuleBuilder.DefineType that returns an instance of System.Reflection.Emit.TypeBuilder As shown in Figure 3-15, the first argument value is the full name of the dynamic .NET type, also called the full path. This means that, instead of using only the class name Person, you should use the namespace and class name combined, RVJ.Core.Person, in this example. In fact, using only part of the .NET type name is a syntax trick of high-level programming languages because the .NET CLR, Framework, or Core recognize only the complete names, and this includes all namespaces. The rules for this way of working in a .NET environment can be found in the ECMA-­335 specification, which is part of the ISA CIL syntax rules. This is fully supported by the VES, is an integral part of the metadata system of the platform, and is fully supported by compilers and tools that emit and work with intermediate code. The Microsoft implementations of the .NET Framework and .NET Core fully support these characteristics and behaviors through the MSIL intermediate language and the CLR execution environment implementations. 108

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Figure 3-15.  Every dynamic .NET type must have a name. In the client code, I supplied RVJ.Core.Person. Figure 3-16 shows that the second argument value is a set of type attributes or flags. As you can see, the ­System.Reflection. TypeAttributes enum is used for the argument value. This means you can combine multiple enum values for your new dynamic .NET type.

Figure 3-16.  System.Reflection.TypeAttributes indicates not only the visibility of a dynamic .NET type but also special characteristics such as if serialization is part of the type-supported functionalities. Figure 3-16 shows the use of the System.Reflection.TypeAttributes enum options for defining a dynamic .NET type. You must be aware that the available values for System.Reflection.TypeAttributes are based on the ISA CIL described by ECMA-335, plus enum options specific to the CLR implementation, in this case, the Microsoft implementation. In fact, all behaviors and types in the System.Reflection.Emit namespace are made for emitting metadata and ISA CIL instructions and not for high-level programming languages such as C#, F#, Visual Basic .NET, or others. You

109

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

must be conscious that System.Reflection.Emit was made to simplify emitting metadata and ISA CIL instructions, in this case MSIL instructions, because you are working with one of the Microsoft implementations of the ECMA-335 specification. This is an excerpt of the introductory text of the Microsoft official documentation about the System.Reflection.Emit namespace:

The System.Reflection.Emit namespace has classes that allow a compiler or a tool to emit metadata and MSIL and optionally generate a PE file on disk. The primary clients of these classes are script engines and compilers. If you need to know more about all the values available for the System. Reflection.TypeAttributes enum, here is the link for the official .NET Framework 4.8 documentation for .NET types: https://docs.microsoft.com/en-us/dotnet/api/system. reflection.typeattributes?f1url=https%3A%2F%2Fmsdn.microsoft. com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DEN-­ US%26k%3Dk(System.Reflection.TypeAttributes);k(DevLang-csharp)% 26rd%3Dtrue&view=netframework-4.8 If you need to know more about all the values available for the System. Reflection.TypeAttributes enum, here is the link for the official .NET Core 3.0 preview 6 documentation for .NET types (remember that this is a preview documentation and can be changed without notice): https://docs.microsoft.com/en-us/dotnet/api/system. reflection.typeattributes?f1url=https%3A%2F%2Fmsdn.microsoft. com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DEN-­ US%26k%3Dk(System.Reflection.TypeAttributes)%3Bk(DevLang-­ csharp)%26rd%3Dtrue&view=netcore-3.0

110

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

To simplify the use of these enum options of System.Reflection. TypeAttributes, I have created a specialized enum that combines common enum options of System.Reflection.TypeAttributes. Figure 3-­17 shows the RVJ.Core.ClassTypeFlags enum values. The combinations of enum options should be expanded as required.

Figure 3-17.  Group of flags indicating the visibility of the dynamic .NET type As shown in Figure 3-18, the last argument value in the method signature is related to inheritance. The argument value should indicate which is the direct ancestor of our dynamic .NET type. As described by the ECMA-335 specification, when not explicitly specifying the direct ancestor, the metadata generator implicitly assumes the ultimate ancestor of all .NET types as the direct ancestor, System.Object. In this example, the direct ancestor of RVJ.Core.Person is System.Object, so you do not need to explicitly specify this. But as part of the explanation about the use of the APIs of System.Reflection.Emit, System.Object is explicitly used as the argument value for the ancestor, or parent.

111

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Figure 3-18.  When a direct ancestor is not specified, System.Object is assumed implicitly Figure 3-19 shows the use of the System.Object .NET type as the direct ancestor that the new dynamic .NET type is extending. The argument value is of System.Type and I am using the typeof operator of the C# programming language to obtain an instance of System.Type for the System.Object .NET type.

Figure 3-19.  RVJ.Core.DynamicModule.DefineDynamicType instance method with argument values for a dynamic .NET type RVJ. Core.Person

 efining Dynamic .NET Field Members D for a Dynamic .NET Type In this section, you will learn about defining a dynamic .NET field type and about a custom library implementation. The RVJ.Core.DynamicModule.DefineDynamicType() instance method returns an instance of the RVJ.Core.DynamicType .NET type. With this instance, you can use the RVJ.Core.DynamicType. DefineDynamicField() method to define the dynamic .NET fields for the dynamic .NET type. 112

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Figure 3-20 shows the client code defining three dynamic .NET fields with private scope.

Figure 3-20.  RVJ.Core.DynamicType.DefineDynamicField() instance method with argument values for defining three dynamic .NET fields with private scope Internally, the RVJ.Core.DynamicType.DefineDynamicField() instance method uses the System.Reflection.Emit.TypeBuilder .NET class, as you can see by the sequence presented in Figure 3-21. Figure 3-21 shows the RVJ.Core.DynamicType.DefineDynamicField() instance method calling a private RVJ.Core.DynamicType.AddField() instance method that creates an instance of the RVJ.Core.DynamicField .NET type that is stored in the private collection of RVJ.Core. DynamicType._dynamicFields.

Figure 3-21.  The RVJ.Core.DynamicType.DefineDynamicField() instance method defining a dynamic .NET field

113

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

In Figure 3-22 you can also see the RVJ.Core.DynamicField constructor implementation, which directly uses the System.Reflection. Emit.TypeBuilder.DefineField() instance method for including the new dynamic .NET field type in the new dynamic .NET type stored in RVJ. Core._typeBuilder.

Figure 3-22.  The first argument value is the name of the new dynamic .NET field. The instance method System.Reflection.Emit.TypeBuilder. DefineField has two implementations, and you are using the first implementation shown in Figure 3-23. The first parameter accepts the name of the dynamic .NET field as an argument value.

Figure 3-23.  The second parameter is the base type of the dynamic .NET field. The second argument value is the base type of the dynamic .NET field and uses System.Type as the parameter type, as you can see in Figure 3-­24. In the client code example, you use the typeof operator of C# to get an instance of the correct System.Type that should be used as the base type for the field.

114

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Figure 3-24.  System.Reflection.FieldAttributes used for defining a dynamic .NET field As shown in Figure 3-­24, the third argument value is of type System. Reflection.FieldAttributes and works in an analogous manner to System.Reflection.TypeAttributes, but it is applied only to fields.

Instantiating a Dynamic .NET Type and  Assigning a Value for a Dynamic .NET Field In this section, you will learn about instantiating a dynamic .NET type and setting and getting a value from a dynamic .NET field of an instance of a dynamic .NET type. Before a dynamic .NET type is ready for instantiation, it should be created. With all dynamic members declared, you need to call the System. Reflection.Emit.TypeBuilder.CreateType instance method to create the dynamic .NET type. An important thing to remember is that the dynamic .NET type is created only once, no matter how many times the System.Reflection. Emit.TypeBuilder.CreateType method is called. To avoid these unnecessary extra calls, in Listing 3-14 you can see that the RVJ.Core. DynamicType.CreateAnInstance instance method does this verification before making the call.

115

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Listing 3-14.  Implementation of RVJ.Core.DynamicType. CreateAnInstance Instance Method public Object CreateAnInstance(String dynamicTypeName) {        if (!this._isTypeCreated) { this._typeCreated = this._ typeBuilder.CreateType(); this._isTypeCreated = true; }        return Activator.CreateInstance(this._typeCreated); } Listing 3-15 shows the client application code as well as the sequence for creating an instance of the dynamic .NET type and shows how to set and get values of dynamic .NET fields for this instance.

Listing 3-15.  Client Code with the Sequence for Instance Creation and Use of Field Members Object person = personDynamicType.CreateAnInstanceOfDynamicType ("RVJ.Core.Person"); Type personType = person.GetType(); FieldInfo personFieldId = personType.GetField("_id", (BindingFlags.NonPublic  | BindingFlags.Instance)); FieldInfo personFieldName = personType.GetField("_name", (BindingFlags.NonPublic | BindingFlags.Instance)); FieldInfo personFieldAge = personType.GetField("_age", (BindingFlags.NonPublic | BindingFlags.Instance)); UInt32 newId = (UInt32) personFieldId.GetValue(person); String newName = (String) personFieldName.GetValue(person); UInt32 newAge = (UInt32) personFieldAge.GetValue(person); if (newName == null) newName = String.Empty; 116

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Console.WriteLine("Before new values...\nperson._id: {0}\ nperson._name: {1}\nperson._age: {2}\n", newId.ToString(), newName, newAge.ToString()); newId = 100; newName = "New Name!!!"; newAge = 25; personFieldId.SetValue(person, newId); personFieldName.SetValue(person, newName); personFieldAge.SetValue(person, newAge); newId = (UInt32) personFieldId.GetValue(person); newName = (String) personFieldName.GetValue(person); newAge = (UInt32) personFieldAge.GetValue(person); Console.WriteLine("After new values assigned...\nperson._id: {0}\nperson._name: {1}\nperson._age: {2}\n", newId.ToString(), newName, newAge.ToString()); Console.ReadLine(); Console.WriteLine("Press to finish...") ;

Summary Here are some do’s and don’ts about using a dynamic .NET assembly.

Do’s •

Do use System.CodeDOM. Tools for code generation are complex, and System.CodeDOM, whose focus is on high-level programming languages such C#, F#, Visual Basic .NET, and others, can be much easier to use than directly working with the System.Reflection.Emit 117

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

namespace types, whose focus is on low-level programming, requiring a specialized knowledge of the semantics of the metadata system, ISA CIL programming as in the ECMA-335 specification, and the behaviors of the target VES system.

118



Do understand the System.Reflection.Emit namespace. It is used by compiler and specialized tools for software development. When you are working in the Microsoft Visual Studio text editor using a .NET high-level programming language as C#, the IDE uses the power of the System.Reflection.Emit .NET types for understanding what you are doing and evaluates in memory what your code means.



If a project needs the full functionality of the System. Reflection.Emit namespace, at the time of writing, do use the full .NET Framework until all the functionality is available for .NET Core and .NET BCL Core.



Do start developing internal tools for learning about the System.Reflection.Emit .NET types.



Do a tool based on the System.Reflection.Emit .NET types certainly will need a reliable database of meta-­ information for the support of their functionalities.



Do a tool based on the System.Reflection.Emit .NET types will need a particularly good GUI application for the support of their functionalities. You must be aware that this can time-consuming.



Do use the System.Reflection.Emit .NET types for a project for any segment of business that a software can be develop.

Chapter 3

Dynamic .NET Assemblies: Defining Dynamic .NET Types

Don’ts •

Don’t create complex code when using the Reflection Services APIs. The System.Reflection.Emit .NET types are extremely performance sensitive.



Don’t use exceptions as “error” code. Create blocks of code to check the argument values and avoid things such as boxing and unboxing, for example.

119

CHAPTER 4

Working with Dynamic .NET Properties In this chapter, you will learn how to define a dynamic .NET property member.

About Dynamic .NET Properties A dynamic .NET property is a named value, backed by a defined dynamic .NET field, that has dynamic .NET methods associated with it that are generically named getter and setter methods. But a dynamic .NET property can also have more dynamic .NET methods associated with it; these methods are generically called other methods. The terms getter, setter, and other methods come from the MSIL .property directive that is used in the metadata for defining them.

The Getter and Setter Methods The MSIL .property directive has a .get member that is used to define the getter method, and a nondynamic or dynamic .NET property can have only one getter method. The MSIL .property directive has a .set member that is used to define the setter method, and a nondynamic or dynamic .NET property can have only one setter method. © Roger Villela 2019 R. Villela, Exploring the .NET Core 3.0 Runtime, https://doi.org/10.1007/978-1-4842-5113-3_4

121

Chapter 4

Working with Dynamic .NET Properties

The Other Methods The MSIL .property directive also has the .other member that is used to define the other methods. A nondynamic or dynamic .NET property can have any number of other methods, and any restrictions are implementation-specific and not defined by the ECMA-335 specification clauses about .NET properties, either nondynamic or dynamic. Listing 4-1 shows an implementation of the RVJ.Core.Example00 .NET class in MSIL. The implementation has one private .NET field and one public .NET property with a getter and setter method for the private .NET field _name.

Listing 4-1.  Implementation of a .NET Property’s Getter and Setter .NET Methods /∗ Author: Roger Villela E-mail: [email protected] Instructions To compile this, open the Developer Command Prompt of Microsoft Visual Studio or .NET Framework SDK, write the following command for creating a 32-bit executable: /∗ ilasm  MSIL_Property.il OR for creating a 64-bit executable: ilasm /X64 MSIL_Property.il ∗/ .assembly extern mscorlib{} .assembly RVJ.Core {}

122

Chapter 4

Working with Dynamic .NET Properties

.class public auto ansi beforefieldinit RVJ.Core.Example00 extends System.Object {        .field private string _name        .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed {           ldarg.0           call       instance void [mscorlib]System.Object::. ctor()           ret        }        .property instance string Name() {               .get instance string RVJ.Core.Example00::get_Name()               .set instance void RVJ.Core.Example00::set_ Name(string)        }        .method public hidebysig specialname instance string get_Name() cil managed {               .locals init (string newName)                   ldarg.0                  ldfld      string RVJ.Core.Example00::_name                   stloc.0                  ldloc.0                  ret         }         .method public hidebysig specialname instance void  set_Name(string 'value') cil managed {

123

Chapter 4

Working with Dynamic .NET Properties

                  ldarg.0                   ldarg.1                   stfld      string RVJ.Core.Example00::_name                 ret         }        .method static public void Main() cil managed {               .entrypoint               ret         } }

The Prefixes get_ and set_ Highlighted in Listing 4-1 is the definition of the private .NET field RVJ. Core.Example00::_name, the definition of the .NET property RVJ.Core. Example00::Name, and the definition of the getter and setter .NET methods RVJ.Core.Example00::get_Name() and RVJ.Core.Example00::set_ Name(), respectively. The prefixes get_ and set_ are not requirements of the CTS but are part of a naming pattern from CLS Rule 28 – I.10.4 Naming Patterns, so every CLS-compliant compiler and programming language should apply this naming convention (see Figure 4-1).

124

Chapter 4

Working with Dynamic .NET Properties

Figure 4-1.  Excerpt of CLS rule describing naming pattern for .NET property definition

The specialname and rtspecialname Attributes One of the pillars of the .NET platform is to be language-agnostic. To achieve this goal, a set of rules was defined in the ECMA-335 specification. The CTS and CLS are two sets of rules that support the language-agnostic goal but are not restricted to this purpose only. These two sets of rules will be followed by every .NET-compliant implementation. Just as a reminder, the CTS is the set of rules for the system of types of the .NET platform that are described in the ECMA-335 specification. Within these rules you can find out about casting, naming conventions, inheritance, type members, and property behaviors, to name a few things. Also highlighted in Listing 4-1 are the attributes specialname and rtspecialname in the definition of the constructor RVJ.Core. Example00::.ctor. The attribute rtspecialname means “runtime special name,” and it relates to the CLR implementation. That is, the runtime environment implements a specific set of rules for the element marked with this attribute. Every item marked with the rtspecialname attribute

125

Chapter 4

Working with Dynamic .NET Properties

has a special significance for the runtime environment, and it is treated in a special manner (implementation-defined). Even though every constructor is a method, it is a special kind of method that has characteristics related to object-oriented programming such as inheritance and chain of calling, for example. The other highlighted attribute is specialname, which also aggregates a specific meaning for the item marked with it, not for the runtime environment but for what the ECMA-335 specification calls other tools. One example of this type of tool is a compiler, which can use this attribute to emit additional code to support specific rules required by the CTS and CLS. For example, the CLS rules require that every getter method, setter method, and other method used with a .NET property definition should be marked with the attribute specialname, and highlighted in Listing 4-1. In the folder \Projects\RVJ\2019\Platforms\CLR\ Code\ExploringDotNETCoreRuntime30\Ch04\Listing_4_1\, you will find the file MSIL_Property.il used for the examples of Listing 4-1 and Listing 4-2. You will also find the file MSIL_Property.il as part of the project Listing_4_1, as shown in Figure 4-2.

126

Chapter 4

Working with Dynamic .NET Properties

Figure 4-2.  The file MSIL_Property.il is part of project Listing_4_1, but it is not compiled Listing 4-2 shows an example with two more methods defined for the .NET property name. These two methods are defined using the attribute .other, as you can see, which can use any number of .NET methods. The methods are not called automatically in the sequence in which they are 127

Chapter 4

Working with Dynamic .NET Properties

defined. Like with any other .NET method, you need to call explicitly via code in an application or a tool such a compiler should generate the code blocks necessary for these callings.

Listing 4-2.  Implementation of Two Methods Using the Attribute .other /∗ Author: Roger Villela E-mail: [email protected] Instructions For compile this, open the Developer Command Prompt of Microsoft Visual Studio or .NET Framework SDK, write the following command for generating a 32-bit executable: /∗ ilasm  MSIL_Property.il OR for generating a 64-bit executable: ilasm /X64 MSIL_Property.il ∗/ .assembly extern mscorlib{} .assembly RVJ.Core {} .class public auto ansi beforefieldinit RVJ.Core.Example00 extends System.Object {        .field private string _name        .method public hidebysig specialname rtspecialname instance void  .ctor() cil managed {

128

Chapter 4

Working with Dynamic .NET Properties

              .maxstack 4           ldarg.0           call       instance void ­[mscorlib]System.Object::. ctor()           ret        } /∗ end of constructor RVJ.Core.Example00::.ctor ∗/        .property instance string Name() {               .get instance string RVJ.Core.Example00::get_Name()                .set instance void RVJ.Core.Example00::set_ Name(string)                .other instance void RVJ.Core. Example00::VerifyName(string)                .other instance void RVJ.Core.Example00::AnotherV erifyName(string)        } /∗ end of property Example00::Name ∗/        .method public hidebysig specialname instance void VerifyName( string name ) cil managed {               .maxstack 8               .locals init ( int32 length )                ldstr "VerifyName() method called. Length of name: {0}\n\n"               ldarg.1  /∗ Load the name argument value. ∗/                callvirt instance int32 [mscorlib]System. String::get_Length()                stloc.0  /∗ Stores the value on length  local variable. ∗/               ldloca length

129

Chapter 4

Working with Dynamic .NET Properties

           callvirt instance string valuetype [mscorlib]System. Int32::ToString()               call void [mscorlib]System.Console::WriteLine( string, object )               ret        }        .method public hidebysig specialname instance void AnotherVerifyName(string name) cil managed {               .maxstack 8               ldarg.1 /∗ Load the name argument value. ∗/               callvirt instance int32 [mscorlib] System. String::get_Length()               pop               callvirt instance string valuetype [mscorlib] System.Int32::ToString()               ldstr "AnotherVerifyName() method called. Length of value: {0}"               call void [mscorlib]System.Console::WriteLine( string, object )               ret        }        .method public hidebysig specialname instance string get_Name() cil managed {        .maxstack 4        .locals init (string currentName)     ldarg.0     ldfld      string RVJ.Core.Example00::_name 130

Chapter 4

Working with Dynamic .NET Properties

    stloc.0        ldloc.0    ret } /∗ end of method Example00::get_Name ∗/ .method public hidebysig specialname instance void  set_ Name(string 'value') cil managed {        .maxstack 4     ldarg.0 /∗ Load this pointer. ∗/     ldarg.1 /∗ Load the 'value' argument value ∗/     stfld      string RVJ.Core.Example00::_name        ret } /∗ end of method Example00::set_Name ∗/ .method public static  hidebysig void Main() cil managed {        .maxstack 8        .entrypoint        .locals init ( class RVJ.Core.Example00 myInstance )        newobj instance void RVJ.Core.Example00::.ctor()        stloc.0 /∗ myInstance ∗/        ldloc.0 /∗ myInstance ∗/        call void [mscorlib]System.Console::Clear()        ldstr "New Name"        callvirt instance void RVJ.Core.Example00::VerifyName( string )        ldloc.0 /∗ myInstance ∗/        ldstr "New Name"        callvirt instance void RVJ.Core.Example00::set_Name( string ) 131

Chapter 4

Working with Dynamic .NET Properties

       ldstr "Name: {0}"        ldloc.0        callvirt instance string RVJ.Core.Example00::get_Name()        call void [mscorlib]System.Console::WriteLine( string, object )        ldstr "\n\nPress to finish..."        call void [mscorlib]System.Console::WriteLine(string)        call string [mscorlib]System.Console::ReadLine()        pop        ret } } /∗ end of class RVJ.Core.Example00 ∗/ In the folder \Projects\RVJ\2019\Platforms\ CLR\Code\ExploringDotNETCoreRuntime30\Ch04\Listing_4_1\ are two folders, the Listing_4_1 folder where the console client is available, and RVJ.Core where your project library is available, which encapsulates the System.Reflection.Emit .NET types for the examples. Open the solution \Projects\RVJ\2019\ Platforms\CLR\Code\ExploringDotNETCoreRuntime30\Ch04\ Listing_4_1\Listing_4_1.sln. The sample code in Listing 4-3 shows how to define a dynamic .NET property and the getter and setter methods. Listing 4-3 shows the client code of Listing_4_1.csproj that uses the RVJ.Core.dll library, encapsulating functionality from System. Reflection.Emit with a focus on productivity. This is the same code used in Chapter 3, but with dynamic .NET properties included. Now, you are using the dynamic .NET properties defined for each dynamic .NET field to do the same operations of “get” and “set” values. A dynamic .NET property is associated with a dynamic .NET field, and by default, the implementation of the IDynamicField.DefineDynamicProperty instance

132

Chapter 4

Working with Dynamic .NET Properties

method defines both get and set accessor methods. You should use the RVJ.Core.PropertyFlags enum values to indicate the characteristics of a dynamic .NET property. Despite that getter and setter methods are typical .NET methods, the relationship with the .NET property, nondynamic or dynamic, is defined via the metadata .property directive. As a result of this, first you need to get a reference to the method that should be called through the metadata information stored through the .property directive. The getter and setter methods are called using the System.Type.InvokeMember() instance method, returning typical information such as the property name, the instance of the target object, and one special argument value, the System. Reflection.BindingFlags enum. You should return the System.Reflection.BindingFlags. GetProperty enum value to the getter method being called and return the System.Reflection.BindingFlags.SetProperty enum value to the setter method being called, as highlighted in Listing 4-3.

Listing 4-3.  Client Code for the RVJ.Core Encapsulating Functionality of the System.Reflection.Emit .NET Types using System; using System.Reflection; using RVJ.Core; namespace Listing_4_1 {        public class Program {               static void Pause( Boolean finish = false, Boolean clearConsole = true ) {                      const String PressMessage = "Press ";                      const String PauseMessage = " to continue...";                      const String FinishMessage = " to finish...";

133

Chapter 4

Working with Dynamic .NET Properties

                      Console.WriteLine( "{0}{1}", PressMessage, ( !finish ? PauseMessage : FinishMessage ) );                      Console.ReadLine();                      if ( clearConsole ) Console.Clear();                      return;               }               static void Main() {                      String assemblyName = "RVJ.Core.Person";                      IDynamicBuilder personBuilder = new DynamicBuilder( assemblyName );                      IDynamicAssembly personDynamicAssembly = personBuilder.DefineDynamicAssembly();                       IDynamicModule personDynamicModule = personDynamicAssembly.DefineDynamicModule();                      IDynamicType personDynamicType = personDynamicModule.DefineDynamicType( assemblyName, ClassTypeFlags.Public, typeof( System.Object ) );                      IDynamicField _id_DynamicField = personDynamicType.DefineDynamicField( "_ id", typeof( System.UInt32 ), FieldFlags. Private );                      IDynamicField _name_DynamicField = personDynamicType.DefineDynamicField( "_name", typeof( System.String ), FieldFlags.Private );

134

Chapter 4

Working with Dynamic .NET Properties

                      IDynamicField _age_DynamicField = personDynamicType.DefineDynamicField( "_ age", typeof( System.UInt32 ), FieldFlags. Private );                       _id_DynamicField.DefineDynamicProperty( "Id", typeof( System.UInt32 ), PropertyFlags.Public | PropertyFlags. ReadAndWrite );                       _name_DynamicField.DefineDynamicProperty( "Name", typeof( System.String ), PropertyFlags.Public | PropertyFlags. ReadAndWrite );                       _age_DynamicField.DefineDynamicProperty( "Age", typeof( System.UInt32 ), PropertyFlags.Public | PropertyFlags. ReadAndWrite );                       Object person = personDynamicType. CreateAnInstance( "RVJ.Core.Person" );                      Type personType = person.GetType();                       FieldInfo personFieldId = personType. GetField( "_id", ( BindingFlags. NonPublic  | BindingFlags.Instance ) );                       FieldInfo personFieldName = personType. GetField( "_name", ( BindingFlags. NonPublic  | BindingFlags.Instance ) );                       FieldInfo personFieldAge = personType. GetField( "_age", ( BindingFlags. NonPublic  | BindingFlags.Instance ) );

135

Chapter 4

Working with Dynamic .NET Properties

                     UInt32 newId = ( UInt32 ) personFieldId. GetValue( person );                      String newName = ( String ) personFieldName.GetValue( person );                      UInt32 newAge = ( UInt32 ) personFieldAge. GetValue( person );                      if ( newName == null ) newName = String.Empty;                      Console.WriteLine( "Before new values...\ nperson._id: {0}\nperson._name: {1}\ nperson._age: {2}\n", newId.ToString(), newName, newAge.ToString() );                      newId = 100;                      newName = "New Name!!!";                      newAge = 25;                      personFieldId.SetValue( person, newId );                      personFieldName.SetValue( person, newName );                      personFieldAge.SetValue( person, newAge );                      newId = ( UInt32 ) personFieldId.GetValue( person );                      newName = ( String ) personFieldName. GetValue( person );                      newAge = ( UInt32 ) personFieldAge. GetValue( person );                      Console.WriteLine( "After new values assigned...\nperson._id: {0}\nperson._ name: {1}\nperson._age: {2}\n", newId. ToString(), newName, newAge.ToString() );

136

Chapter 4

Working with Dynamic .NET Properties

                     Program.Pause();                       newId = ( UInt32 ) personType. InvokeMember( "Id", BindingFlags. GetProperty, null, person, null );                       newName = ( String ) personType. InvokeMember( "Name", BindingFlags. GetProperty, null, person, null );                       newAge = ( UInt32 ) personType. InvokeMember( "Age", BindingFlags. GetProperty, null, person, null );                       Console.WriteLine( "Before new values assigned...\nperson._id: {0}\nperson._ name: {1}\nperson._age: {2}\n", newId. ToString(), newName, newAge.ToString() );                      newId = 200;                       newName = "New Name using a dynamic .NET Property!!!";                      newAge = 35;                      personType.InvokeMember( "Id", BindingFlags. SetProperty, null, person, new Object[] { newId } );                      personType.InvokeMember( "Name", BindingFlags. SetProperty, null, person, new Object[] { newName } );                      personType.InvokeMember( "Age", BindingFlags. SetProperty, null, person, new Object[] { newAge } );

137

Chapter 4

Working with Dynamic .NET Properties

                     Console.WriteLine( "After new values...\ nperson._id: {0}\nperson._name: {1}\ nperson._age: {2}\n", newId.ToString(), newName, newAge.ToString() );                      Program.Pause( true );               }        }; };

Implementing a Dynamic .NET Property As you can see in Listing 4-3, a group of .NET types is defined in the RVJ. Core project, just like in the implementation in Chapter 3. This section shows the source code for each these .NET types and explains the organization of the project. The interfaces represent the principal .NET types in common use when you are writing code using typical software development tools for .NET development. Figure 4-3 shows the interface RVJ.Core.IDynamicProperty in the file IDynamicProperty.cs.

138

Chapter 4

Working with Dynamic .NET Properties

Figure 4-3.  The Solution Explorer with the RVJ.Core project, with all the .NET types for a dynamic .NET property implementation highlighted

139

Chapter 4

Working with Dynamic .NET Properties

Every .NET property, nondynamic or dynamic, is related to a .NET field, nondynamic or dynamic, but pertains to a .NET type, nondynamic or dynamic. For this purpose, the RVJ.Core.DynamicField .NET class implements the .NET method RVJ.Core.DynamicField. DefineDynamicProperty, which is declared in the interface RVJ.Core. IDynamicField, as shown in Listing 4-4 and Listing 4-5.

Listing 4-4.  Declaration of RVJ.Core.IDynamicField and Members using System; namespace RVJ.Core {        public interface IDynamicField {               IDynamicProperty DefineDynamicProperty( String propertyName, Type propertyType, PropertyFlags flags );        }; };

Listing 4-5.  Definition of RVJ.Core.DynamicField That Encapsulates the Functionality of the System.Reflection.Emit.FieldBuilder Class and Members using System; using System.Reflection; using System.Reflection.Emit; namespace RVJ.Core {        public class DynamicField : IDynamicField {               private readonly FieldBuilder _fieldBuilder;               private readonly TypeBuilder _typeBuilder;

140

Chapter 4

Working with Dynamic .NET Properties

              protected DynamicField() : base() {                      return;               }               protected DynamicField( TypeBuilder builder ) : this() {                      this._typeBuilder  = builder;                      return;               }               protected internal DynamicField( String fieldName, Type fieldType, FieldFlags flags, TypeBuilder builder ) : this( builder ) {                      this._fieldBuilder = this._typeBuilder. DefineField( fieldName,                             fieldType,                             ( ( FieldAttributes ) flags ) );                      return;               }               private IDynamicProperty AddProperty( String propertyName, Type propertyBaseType, PropertyFlags flags, FieldBuilder fieldBuilder, TypeBuilder builder ) {                      return new DynamicProperty( propertyName, propertyBaseType, flags, fieldBuilder, builder );               }               protected internal TypeBuilder _getTypeBuilder() {                      return this._typeBuilder;               }

141

Chapter 4

Working with Dynamic .NET Properties

              protected internal FieldBuilder _ getFieldBuilder() {                      return this._fieldBuilder;               }               public IDynamicProperty DefineDynamicProperty( String propertyName, Type propertyType, PropertyFlags flags ) {                      DynamicProperty dynamicProperty = ( DynamicProperty ) this.AddProperty( propertyName, propertyType, flags, this._ fieldBuilder, this._typeBuilder );                      if ( flags.HasFlag( PropertyFlags. ReadAndWrite ) ) {                             dynamicProperty.DefineGetMethod();                             dynamicProperty.DefineSetMethod();                      } else if ( flags.HasFlag( PropertyFlags. ReadOnly ) ) dynamicProperty. DefineGetMethod();                      else if ( flags.HasFlag( PropertyFlags. WriteOnly ) ) dynamicProperty. DefineSetMethod();                      return dynamicProperty;               }        } }; Listing 4-6 and Listing 4-7 show the interface RVJ.Core. IDynamicProperty and the implementation of the RVJ.Core. DynamicProperty .NET class type, respectively. For now, the interface RVJ. Core.IDynamicProperty has no members. 142

Chapter 4

Working with Dynamic .NET Properties

Listing 4-6.  Declaration of RVJ.Core.IDynamicProperty and Members namespace RVJ.Core {        public interface IDynamicProperty {} }; The System.Reflection.Emit.TypeBuilder.DefineProperty() instance method is used to define a dynamic .NET property. The System.Reflection.PropertyAttributes enum is a group of flags that can be combined to define characteristics of a dynamic .NET property. For example, the System.Reflection.PropertyAttributes.HasDefault enum option defines that the underlying field associated with the property should have a default value based on the base .NET type defined for the .NET field.

Listing 4-7.  Definition of RVJ.Core.DynamicProperty That Encapsulates the Functionalities of the System.Reflection.Emit. DynamicProperty and Members using System; using System.Reflection; using System.Reflection.Emit; namespace RVJ.Core {        public class DynamicProperty : IDynamicProperty {               private readonly TypeBuilder _typeBuilder;               private readonly FieldBuilder _fieldBuilder;               private readonly PropertyBuilder _propertyBuilder;               private readonly PropertyAttributes _ propertyAttributes;               private MethodBuilder _getMethodBuilder;               private MethodBuilder _setMethodBuilder;               private const String _Get_Method_Prefix = "get_";               private const String _Set_Method_Prefix = "set_"; 143

Chapter 4

Working with Dynamic .NET Properties

              protected DynamicProperty() : base() {return;}               protected DynamicProperty( TypeBuilder builder, FieldBuilder fieldBuilder ) : this() {                      this._typeBuilder  = builder;                      this._fieldBuilder = fieldBuilder;                      return;               }               protected internal DynamicProperty( String propertyName, Type propertyBaseType, PropertyFlags flags, FieldBuilder fieldBuilder, TypeBuilder typeBuilder ) : this( typeBuilder, fieldBuilder ) {                      this._propertyBuilder = this._ typeBuilder.DefineProperty( propertyName, PropertyAttributes.HasDefault, propertyBaseType, null );                      this._propertyAttributes = ( PropertyAttributes ) flags;                      return;               }               private TypeBuilder TypeBuilder {                      get { return this._typeBuilder; }               }               private FieldBuilder FieldBuilder {                      get { return this._fieldBuilder;}               } 144

Chapter 4

Working with Dynamic .NET Properties

              private MethodBuilder GetMethodBuilder {                      get { return this._getMethodBuilder; }                      set { this._getMethodBuilder = value; }               }               private MethodBuilder SetMethodBuilder {                      get { return this._setMethodBuilder;}                      set { this._setMethodBuilder = value; }               }               private PropertyBuilder PropertyBuilder {                      get {return this._propertyBuilder;}               }               protected internal void DefineSetMethod() {                      MethodBuilder setMethodBuilder = this. SetMethodBuilder;                      PropertyBuilder setPropertyBuilder = this. PropertyBuilder;                      setMethodBuilder = this.TypeBuilder. DefineMethod( setPropertyBuilder. Name.Insert( 0, DynamicProperty._Set_ Method_Prefix ), ( MethodAttributes. HideBySig| MethodAttributes.Public  | MethodAttributes.SpecialName ), null, new Type[] { setPropertyBuilder.PropertyType } );

145

Chapter 4

Working with Dynamic .NET Properties

                     ILGenerator MSIL_Set_Generator = setMethodBuilder.GetILGenerator();                      MSIL_Set_Generator.Emit( OpCodes.Ldarg_0 );                      MSIL_Set_Generator.Emit( OpCodes.Ldarg_1 );                      MSIL_Set_Generator.Emit( OpCodes.Stfld, this.FieldBuilder );                      MSIL_Set_Generator.Emit( OpCodes.Ret );                      setPropertyBuilder.SetSetMethod( setMethodBuilder );                      return;               }               protected internal void DefineGetMethod() {                      MethodBuilder getMethodBuilder = this. GetMethodBuilder;                      PropertyBuilder getPropertyBuilder = this. PropertyBuilder;                      getMethodBuilder = this.TypeBuilder. DefineMethod( getPropertyBuilder. Name.Insert( 0, DynamicProperty._Get_ Method_Prefix ), ( MethodAttributes ) ( MethodAttributes.HideBySig| MethodAttributes.Public  | MethodAttributes.SpecialName ), getPropertyBuilder.PropertyType, Type. EmptyTypes );                      ILGenerator MSIL_Get_Generator = getMethodBuilder.GetILGenerator();                      MSIL_Get_Generator.Emit( OpCodes.Ldarg_0 );

146

Chapter 4

Working with Dynamic .NET Properties

                     MSIL_Get_Generator.Emit( OpCodes.Ldfld, this.FieldBuilder );                      MSIL_Get_Generator.Emit( OpCodes.Ret );                      getPropertyBuilder.SetGetMethod( getMethodBuilder );                      return;               }        } }; The System.Reflection.PropertyAttributes enum has no options encapsulated in the RVJ.Core.PropertyFlags enum, as shown in Listing 4-8. Highlighted in Listing 4-8 are values for the methods that back the dynamic .NET property. The values in the RVJ.Core.PropertyFlags enum are used for the client code, choosing between definitions for getter-only (RVJ.Core.PropertyFlags.ReadOnly), setter-­only (RVJ.Core.PropertyFlags.WriteOnly), or getter and setter (RVJ.Core.PropertyFlags.ReadAndWrite) methods for the dynamic .NET property. This is shown by the highlighted RVJ.Core.DynamicField. DefineDynamicProperty() instance method in Listing 4-5.

Listing 4-8.  Declaration of RVJ.Core.PropertyFlags and Its Options using System; using System.Reflection; namespace RVJ.Core {        [Flags]        public enum PropertyFlags {               Private = MethodAttributes.SpecialName | MethodAttributes.Private | MethodAttributes. HideBySig, 147

Chapter 4

Working with Dynamic .NET Properties

              Public = MethodAttributes.SpecialName | MethodAttributes.Public | MethodAttributes. HideBySig,               Static = MethodAttributes.SpecialName | MethodAttributes.Static,               WriteOnly = MethodAttributes.SpecialName | MethodAttributes.HideBySig | 100,               ReadOnly = MethodAttributes.SpecialName | MethodAttributes.HideBySig | 200,               ReadAndWrite = MethodAttributes.SpecialName | MethodAttributes.HideBySig | 300        } }; You should define set and get accessor methods for your dynamic .NET property implementation, as this is required by the implementation of the sample application too. From the perspective of the metadata system of the .NET platform, the definition of get and set accessor methods is not required for a property to be a valid metadata item. Having a getter accessor method, having a setter accessor method, and having both accessor methods are topics that are touched by the CTS and a set of CLS rules (CLS Rule 24, CLS Rule 26, CLS Rule 27, and CLS Rule 28) defined in the ECMA-335 specification. Specifically, in Partition – I – Section 8.11.3 – Property Definitions, there is a description of a .NET property definition with the following fundamental characteristics:

148



It defines a named value and getter and setter methods that access this named value.



A .NET property definition is always part of a .NET interface definition or of a .NET class definition, both of which are reference types.

Chapter 4

Working with Dynamic .NET Properties



The name and value of a .NET property definition is scoped to the .NET type that includes the .NET property definition.



The .NET property definition specifies which getter and setter methods exist and which are their respective method contracts.



An implementation of a .NET type that declares support for a .NET property contract should implement the methods required by that .NET property contract, and these methods’ implementations define how the value is stored and retrieved.



As with any .NET method, the CTS requires that the .NET method contracts that comprise the .NET property match the .NET method implementations.



A .NET property should define a getter method for accessing the current value, but the setter method, for modifying the value, is optional.



In CLS Rule 28 is a description of naming conventions used with the .NET event and .NET property elements. Despite that the CTS does not dictate the naming of .NET property or .NET event elements, the CLS does specify a pattern that should be observed, as follows: •

Here you have getter and setter methods for the .NET property name of the System.String .NET type of the BCL: •

System.String get_Name();



void set_Name( System.String name );

149

Chapter 4

Working with Dynamic .NET Properties



For the getter method, the pattern is as follows: get_( )



For the setter method, the pattern is as follows: void set_( , )



is an optional parameter and used with a type of .NET property called the indexed property. For example, an instance of System.String is a collection of instances of the System.Char value type. System.String has a .NET property implemented as an indexer to access the instances of System.Char in an instance of System.String, as shown in Listing 4-9 and Listing 4-10.

Listing 4-9.  Indexer .NET Property of the System.String .NET Type, C# Programming Language public System.Char this[System.Int32 index] { get; }

Listing 4-10.  Indexer .NET Property of System::String .NET Type, C++/CLI Projection public: property System::Char default[System::Int32] { System::Char get( System::Int32 index ); };

150

Chapter 4

Working with Dynamic .NET Properties

In the RVJ.Core.DynamicProperty.DefineSetMethod() and RVJ. Core.DynamicProperty.DefineGetMethod() instance methods, you have the System.Reflection.Emit.ILGenerator .NET type, which is responsible for generating MSIL instructions. You must remember that when using the System.Reflection.Emit .NET types, you are working at the lowest level in terms of a managed programming language (that is, in the ISA CIL instructions defined by the ECMA 335 specification and implemented by an environment, in this example, the .NET Core implementation of Microsoft). For the .NET Core implementation provided by Microsoft, the ISA CIL instruction is named MSIL and can have non-ECMA-335 instructions. But for these examples, you are using only the ISA CIL instructions defined by the ECMA-335 specification. In the RVJ.Core.DynamicProperty.DefineSetMethod() and RVJ. Core.DynamicProperty.DefineGetMethod() instance methods, you can use the System.Reflection.Emit.OpCodes .NET class type, where you have a list of MSIL instructions represented by fields with the name of each MSIL-supported instruction. Every System.Reflection.Emit field that represents a MSIL instruction is of the System.Reflection.Emit.OpCode .NET struct type. In Listing 4-11, you can see the complete code for the System. Reflection.Emit.OpCode .NET struct type, and for the list of fields representing the MSIL instructions, you should look at the System. Reflection.Emit.OpCodes .NET class type that contains such a definition. You can access both of these excerpts through the System.Reflection. Primitives.cs source code of the GitHub project corefx of .NET Core that you can access using this link: https://github.com/dotnet/corefx/ blob/master/src/System.Reflection.Primitives/ref/System. Reflection.Primitives.cs.

151

Chapter 4

Working with Dynamic .NET Properties

Listing 4-11.  Definition of System.Reflection.Emit.OpCode .NET Struct Type public readonly partial struct OpCode : IEquatable     {         private readonly object _dummy;         public System.Reflection.Emit.FlowControl FlowControl { get { throw null; } }         public string Name { get { throw null; } }         public System.Reflection.Emit.OpCodeType OpCodeType { get { throw null; } }         public System.Reflection.Emit.OperandType OperandType { get { throw null; } }         public int Size { get { throw null; } }         public System.Reflection.Emit.StackBehaviour StackBehaviourPop { get { throw null; } }         public System.Reflection.Emit.StackBehaviour StackBehaviourPush { get { throw null; } }         public short Value { get { throw null; } }         public override bool Equals(object obj) { throw null; }         public bool Equals(System.Reflection.Emit.OpCode obj) { throw null; }         public override int GetHashCode() { throw null; }         public static bool operator ==(System.Reflection.Emit. OpCode a, System.Reflection.Emit.OpCode b) { throw null; }          public static bool operator !=(System.Reflection.Emit. OpCode a, System.Reflection.Emit.OpCode b) { throw null; }         public override string ToString() { throw null; }     }

152

Chapter 4

Working with Dynamic .NET Properties

Summary Here are some do’s and don’ts about using the dynamic .NET property.

Do’s •

Do wait for the release implementation of .NET Core 3.0. If a project requires all the functionality of the System.Reflection.Emit namespace, use the .NET Framework until all the functionality the project requires is available in the .NET Core and .NET BCL Core frameworks.



Do be aware that when working with the System. Reflection.Emit. It is a low-level API for emitting MSIL instructions and creating metadata data structures.



Do use CodeDOM APIs. When working with a more high-level API for emitting code, consider using the CodeDOM APIs, which abstract the details of the low-­level programming language.

Don’ts •

Don’t use the types in the System.Reflection.Emit namespace when the objective is emitting source code directly in a high-level programming language, such as C# or Visual Basic .NET.



Don’t work with types in the System.Reflection.Emit namespace when the objective isn’t the use of lowlevel APIs for emitting MSIL instructions and creating metadata data structures.

153

CHAPTER 5

Working with the CodeDOM In this chapter, you will learn about using the CodeDOM to generate code.

A  bout the CodeDOM The .NET platform includes a mechanism called the Code Document Object Model (CodeDOM) that enables developers to generate source code in multiple programming languages at run time, based on a single model that represents the code to render. In the CodeDOM, elements are linked to each other with a goal of representing the structure of the source code. This information forms a data structure known as a CodeDOM graph, which models the structure of source code being generated. The types are organized into two namespaces. The System.CodeDom namespace defines what can represent the logical structure of the source code, independent of specific programming language, and the System. CodeDom.Compiler namespace defines types for generating source code from CodeDOM graphs and managing the compilation of the source code in the supported languages.

© Roger Villela 2019 R. Villela, Exploring the .NET Core 3.0 Runtime, https://doi.org/10.1007/978-1-4842-5113-3_5

155

Chapter 5

Working with the CodeDOM

Code Generation Using the CodeDOM You can think about the structure of a CodeDOM graph as a tree of containers. The root, or topmost, container of each compilable CodeDOM graph is a System.CodeDom.CodeCompileUnit object. Every element of the source code model must be linked into the graph through a property of a System.CodeDom.CodeObject class in the CodeDOM graph. System.CodeDom.CodeCompileUnit is an object that can reference a CodeDOM object graph to model the target source code to compile, and the object has properties for storing references to attributes, namespaces, and assemblies used by System.CodeDom.CodeCompileUnit and its elements. The sample solution \Projects\RVJ\2019\ Platforms\CLR\Code\ExploringDotNETCoreRuntime30\Ch05\ Listing_5_1\Listing_5_1.sln includes the project RVJ.Core of type Class Library. Figure 5-1 shows a set of sample .NET types used to demonstrate the functionality of the CodeDOM.

156

Chapter 5

Working with the CodeDOM

Figure 5-1.  .NET types that encapsulate BCL .NET CodeDOM types

157

Chapter 5

Working with the CodeDOM

This set of .NET types encapsulates the functionality of .NET types in the namespaces System.CodeDom and System.CodeDom.Compiler. Listing 5-1 shows an example of how to use these encapsulated .NET types.

Listing 5-1.  Code Client Using Encapsulated .NET Types public static void UsingCodeDOM() {     CodeGenerator codeGenerator = new CodeGenerator();     codeGenerator.ProgrammingLanguage = ProgrammingLanguages. CSharp;     codeGenerator.RootNamespace = "RVJ.Core.Sample";     codeGenerator.SourceFileName = "Person.cs";     AssemblyInformation assemblyInfo = new AssemblyInformation( codeGenerator.RootNamespace, new String[] { "System" } );     codeGenerator.AssemblyInformation = assemblyInfo;     TypeInformation personType = new TypeInformation( "Person", codeGenerator.RootNamespace );     FieldInformation idField = new FieldInformation( "_id", TypeCode.Int32 );     FieldInformation nameField = new FieldInformation( "_name", TypeCode.String );     FieldInformation ageField = new FieldInformation( "_age", TypeCode.Int32 );     idField.Attributes = RVJ.Core.CodeDOM.FieldAttributes.Private;     nameField.Attributes = RVJ.Core.CodeDOM.FieldAttributes.Private;     ageField.Attributes = RVJ.Core.CodeDOM.FieldAttributes.Private;     personType.AddFields( new FieldInformation[] { idField, nameField, ageField } );

158

Chapter 5

Working with the CodeDOM

    assemblyInfo.AddType( personType );     codeGenerator.Generate();     codeGenerator.Save();     return; } The use of the functionality of the .NET types in the namespaces System.CodeDom and System.CodeDom.Compiler is simplified by the .NET types in the source code. These .NET types are as follows: •

CodeGenerator.cs •



AssemblyInformation.cs •



A .NET type for aggregating information about a field and the required information for the context

InterfaceInformation.cs •



A .NET type for aggregating information about the assembly and the required information for the context

FieldInformation.cs •



Responsible for aggregating information and calling functionalities for code generation and saves the generated code on file(s).

A .NET type for aggregating information about the interface type and the required information for the context

MethodInformation.cs •

A .NET type for aggregating information about a method and the required information for the context 159

Chapter 5



Working with the CodeDOM

PropertyInformation.cs •



TypeInformation.cs •



A .NET enum for setting the characteristics of a field, such as private, public, and others

ProgrammingLanguages.cs •



A .NET type for aggregating information about a type and the required information for the context

FieldAttributes.cs •



A .NET type for aggregating information about a property and the required information for the context

A list of programming language providers

TypeInformation.cs •

A .NET enum that indicates whether a .NET type is a reference type, a value type, an interface type, an enum type, or other

To create an object graph for a simple application, you should assemble the source code model and reference it from a CodeCompileUnit. The RVJ.Core.CodeGenerator .NET type shown in Listing 5-2 represents the sequence used by the program to generate the source code.

Listing 5-2.  Source Code of the CodeGenerator .NET Type using using using using

System; System.CodeDom; System.CodeDom.Compiler; System.IO;

namespace RVJ.Core.CodeDOM { 160

Chapter 5

Working with the CodeDOM

    public class CodeGenerator {         private Boolean _hasGenerated;         private CodeDomProvider _provider;         private CodeCompileUnit _compileUnit;         public CodeGenerator() : base() {             this._hasGenerated = new Boolean();             return;         }         public ProgrammingLanguages ProgrammingLanguage { get; set; }         public AssemblyInformation AssemblyInformation { get; set; }         public String RootNamespace { get; set; }         public String SourceFileName { get; set; }         public Boolean Generate() {             return this.__CodeGenerator();         }         public Boolean Save() {             return this.__Save( ref this._compileUnit, ref this._provider );         }         private Boolean __CreateProvider( ProgrammingLanguages programmingLanguage, ref CodeDomProvider provider ) {             Boolean hasTheProvider = new Boolean();             String language = programmingLanguage.ToString();

161

Chapter 5

Working with the CodeDOM

            switch ( programmingLanguage ) {                 case ProgrammingLanguages.CSharp:                     hasTheProvider = CodeDomProvider. IsDefinedLanguage( language );                     break;                 case ProgrammingLanguages.VisualBasic:                     hasTheProvider = CodeDomProvider. IsDefinedLanguage( language );                     break;                 default: /∗  C# Programming Language. ∗/                     language = "C#";                     hasTheProvider = !hasTheProvider;                     break;             };             provider = CodeDomProvider.CreateProvider ( language );             return hasTheProvider;         }         private Boolean __CodeGenerator() {             ProgrammingLanguages pl = this.ProgrammingLanguage;             AssemblyInformation ai = this.AssemblyInformation;             String assemblyName = ai.Name;             String rootNamespace = this.RootNamespace;             TypeInformation[] types = ai.Types;             Int32 index = new Int32();             this.__CreateProvider( pl, ref this._provider );             CodeCompileUnit compileUnit = new CodeCompileUnit();

162

Chapter 5

Working with the CodeDOM

            CodeNamespace rn = new CodeNamespace( rootNamespace );             CodeNamespaceImport[] cnic = new CodeNamespaceImport[ ai.Imports.Length ];             for ( ; index 0 ) ) this._imports = imports;                             return;               }               public void AddType( TypeInformation newType ) {                      if ( newType != null ) this._types. Add( newType );                      return;               } 165

Chapter 5

Working with the CodeDOM

              public String Name {  get { return this._name; } }               public String[] Imports { get { return this._ imports; } }               public TypeInformation[] Types { get { return this._types.ToArray(); } }        } }; Listing 5-4 shows the content of the file FieldAttributes.cs.

Listing 5-4.  Content of the File FieldAttributes.cs using System; using System.CodeDom; namespace RVJ.Core.CodeDOM {        [Flags]        public enum FieldAttributes {               Public = MemberAttributes.Public,               Private = MemberAttributes.Private,        }; }; Listing 5-5 shows the content of the file FieldInformation.cs.

166

Chapter 5

Working with the CodeDOM

Listing 5-5.  Content of the File FieldInformation.cs using System; using System.CodeDom; namespace RVJ.Core.CodeDOM {        public class FieldInformation{               private readonly String _name;               public               public               public               public

readonly CodeTypeReference TypeReference; readonly Type BaseType; readonly TypeCode TypeCode; FieldAttributes Attributes;

              private FieldInformation() : base() { }               public FieldInformation( String name, TypeCode baseTypeCode ): this() {                      if ( !String.IsNullOrEmpty( name ) && !String.IsNullOrWhiteSpace( name ) ) {                             this._name = name;                             this.TypeCode = baseTypeCode;                             this.__SetCodeTypeReference( baseTypeCode, ref this. TypeReference );                             this.BaseType = Type.GetType( this. TypeReference.BaseType );                      }                      return;               } 167

Chapter 5

Working with the CodeDOM

              public FieldInformation( String name, Type baseType ) : this() {                      if ( ( !String.IsNullOrEmpty( name ) && ( !String.IsNullOrWhiteSpace( name ) && ( baseType != null ) ) ) ) {                             this.__SetCodeTypeReference( baseType, ref this.TypeReference );                             this.BaseType = Type.GetType( this. TypeReference.BaseType );                      };                      return;               }               private void __SetCodeTypeReference( TypeCode typeCode, ref CodeTypeReference typeReference ) {                      Type baseType;                      switch ( typeCode ) {                             case TypeCode.String: {                                    baseType = typeof( String );                                    break;                             };                             case TypeCode.Int32: {                                    baseType = typeof( Int32 );                                    break;                             }; 168

Chapter 5

Working with the CodeDOM

                            case TypeCode.UInt32: {                                    baseType = typeof( UInt32 );                                    break;                             };                             default: {                                    baseType = typeof( Object );                                    break;                             };                      };                       typeReference = new CodeTypeReference( baseType );                      return;               }                private void __SetCodeTypeReference( Type baseType, ref CodeTypeReference typeReference ) {                       if(  ( baseType != null ) && ( typeReference != null ) ) typeReference = new CodeTypeReference( baseType );                       typeReference = new CodeTypeReference( baseType );                      return;               }               public String Name { get { return this._name;  } }        }; }; 169

Chapter 5

Working with the CodeDOM

Listing 5-6 shows the content of the file InterfaceInformation.cs.

Listing 5-6.  Content of the File InterfaceInformation.cs using System; using System.Collections.Generic; namespace RVJ.Core.CodeDOM {        public class InterfaceInformation {               private readonly String _name;               private readonly List ­_methods;               private readonly List _properties;               private InterfaceInformation(): base() { }               public InterfaceInformation( String name ) : this(){                      if ( !String.IsNullOrEmpty( name ) && !String.IsNullOrWhiteSpace( name ) ) this._name = name;               }               public List Properties {                      get {                             return this._properties;                      }               }               public List Methods {                      get {

170

Chapter 5

Working with the CodeDOM

                            return this._methods;                      }               }        } }; Listing 5-7 shows the content of the file MethodInformation.cs.

Listing 5-7.  Content of the File MethodInformation.cs using System; using System.Collections.Generic; namespace RVJ.Core.CodeDOM {        public class MethodInformation {               private readonly String _name;               private MethodInformation() : base() { return;  }               public MethodInformation( String name ): this() {                      if ( !String.IsNullOrEmpty( name ) && !String.IsNullOrWhiteSpace( name ) ) this._name = name;                      return;               }               public String Name {  get { return this._name;  } }        } };

171

Chapter 5

Working with the CodeDOM

Listing 5-8 shows the content of the file ProgrammingLanguages.cs.

Listing 5-8.  Content of the File ProgrammingLanguages.cs namespace RVJ.Core.CodeDOM {        public enum ProgrammingLanguages        {               CSharp,               VisualBasic        } }; Listing 5-9 shows the content of file PropertyInformation.cs.

Listing 5-9.  Content of the File PropertyInformation.cs using System; namespace RVJ.Core.CodeDOM {        public class PropertyInformation {               private readonly String _name;               private PropertyInformation() : base() { return;  }               public PropertyInformation( String name ): this() {                      if ( !String.IsNullOrEmpty( name ) && !String.IsNullOrWhiteSpace( name ) ) this._name = name;                      return;               }               public String Name { get { return this._name; } }        } }; 172

Chapter 5

Working with the CodeDOM

Listing 5-10 shows the content of the file TypeInformation.cs.

Listing 5-10.  Content of the File TypeInformation.cs using System; using System.Collections.Generic; namespace RVJ.Core.CodeDOM {        public class TypeInformation {               private readonly               private readonly               private readonly               private readonly               private readonly properties;               private readonly               private readonly interfaces;

String _name; String _namespace; String _fullName; List _fields; List _ List _methods; List _

              private TypeInformation() : base() {                      this._fields = new List();                      this._interfaces = new List();                      this._methods = new List();                      this._properties = new List();                      return;               }

173

Chapter 5

Working with the CodeDOM

              public TypeInformation( String name, String namespaceOfType ) : this() {                      if ( !String.IsNullOrEmpty( name ) && !String.IsNullOrWhiteSpace( name ) ) {                              if ( !String.IsNullOrEmpty( namespace OfType ) && !String.IsNullOrWhite Space( namespaceOfType ) ) {                                    this._name = name;                                    this._namespace = namespaceOfType;                                     this._fullName = this._ namespace + "." + this._name;                             }                      }                      return;               }               public void AddField( FieldInformation newField ) {                      if ( newField != null ) this._fields.Add ( newField );                      return;               }               public void AddFields( FieldInformation[] newFields ) {                      if ( ( newFields != null ) && ( newFields. Length > 0 ) ) this._fields.AddRange( newFields ); 174

Chapter 5

Working with the CodeDOM

                     return;               }               public TypeClassification Classification { get; set; }                public String Namespace {  get { return this._ namespace; }  }               public String Name {  get { return this._name; } }                public String FullName {  get { return this._ fullName; } }               public FieldInformation[] Fields {                      get {                             return this._fields.ToArray();                      }               }               public PropertyInformation[] Properties {                      get {                             return this._properties.ToArray();                      }               }               public MethodInformation[] Methods {                      get {                             return this._methods.ToArray();                      }               }

175

Chapter 5

Working with the CodeDOM

              public InterfaceInformation[] Interfaces {                      get {                             return this._interfaces.ToArray();                      }               }        } }; Listing 5-11 shows the content of the file TypeClassification.cs.

Listing 5-11.  Content of the File TypeClassification.cs namespace RVJ.Core.CodeDOM {        public enum TypeClassification {               ReferenceType,               ValueType,               InterfaceType,               EnumType        } };

S  ummary Here are some do’s and don’ts regarding the use of the .NET CodeDOM.

176

Chapter 5

Working with the CodeDOM

Do’s •

Do use CodeDOM because .NET types of System. CodeDom represents the logical structure of the source code, and is not specific of a programming language.



Do use CodeDOM when needs generates source code from CodeDOM logical structure.



Do use CodeDOM when needs compiles source code generated from CodeDOM logical structure.

Don’ts •

Don’t use CodeDOM logical model to emits source code for technologies such as P/Invoke and others scenarios for interoperability. Prefer code written by a specialist on these scenarios of interoperability.



Don’t consider uses CodeDOM when the purpose is emits source code using MSIL.



Don’t work with the purpose of creates a large base of source code created automatically by CodeCOM logical structure. This can creates difficult for maintenance of such code base.

177

Index A, B

ProgrammingLanguages.cs, content, 172 PropertyInformation.cs, content, 172 System.CodeDom namespace, 155 System.CodeDom.Compiler namespace, 155 TypeClassification.cs, content, 176 TypeInformation.cs, content, 173–176

Artificial intelligence (AI) models, 84 AssemblyInfoView, 77, 78

C CLR Core implementation, 38, 39, 41, 42, 55, 65 Code Document Object Model (CodeDOM) AssemblyInformation.cs, content, 165, 166 CodeGenerator .NET Type, 160–164 encapsulated .NET types, 158 FieldAttributes.cs, content, 166 FieldInformation.cs, content, 166–169 functionality, 156, 157 graph, 155 InterfaceInformation.cs, content, 170, 171 MethodInformation.cs, content, 171 .NET platform, 155 .NET types, 159, 160 © Roger Villela 2019 R. Villela, Exploring the .NET Core 3.0 Runtime, https://doi.org/10.1007/978-1-4842-5113-3

D Development environment, 36, 37 Dynamic assemblies, 83 .NET type and field, 115–117 RVJ.Core.IDynamicType. DefineDynamicField instance method, 86 RVJ.Core.Person dynamic .NET class type, 86 System.Reflection.Emit namespace, 84 System.Reflection.Emit .NET Types, 87–89

179

INDEX

Dynamic .NET property CLS rule, 125 getter/setter method, 121–124 implementation accessor methods, 148 fundamental characteristics, 148–150 RVJ.Core.IDynamicField and Members, declaration, 140 RVJ.Core.IDynamicProperty and Members, declaration, 143 RVJ.Core project, 139 RVJ.Core.PropertyFlags, declaration, 147 System.Reflection.Emit. DynamicProperty and Members, 143, 144, 146, 147 System.Reflection.Emit. FieldBuilder Class and Members, 140–142 System.Reflection.Emit. OpCode .NET Struct Type, 152 specialname/rtspecialname attributes (see specialname/ rtspecialname attributes)

E, F Extensible Application Markup Language (XAML), 4, 22–25

180

G, H, I, J, K, L Global Assembly Cache (GAC), 68

M Metadata system administrator user, 46 application tab, configuration, 47 binary folder, 51 CLI ECMA-335 specification, 41 CLI loader, 43 command prompt, 50 data pointer, 43 data stream, 43 data structures, 43 default executable, 47–49 dotnet clean command, 46 dotnet tool, 45 high-level perspective, 42 ILDASM tool, 50, 52, 53 .NET module, 42 operating system, 47 project properties, 44 RVJ.Core content, 54 static .NET assembly, 51 tables, 43 Microsoft Visual Studio 2019, 5 project templates, 7 start window, 6 ModuleInfoView, 77, 78

INDEX Index

N, O

P, Q

.NET assembly adstract models, 39 CLR core implementation, 38 core library sets, 39 data streams, 38, 40 deployment unit, 38 development environment, 36, 37 fundamental data structures, 41 manifest, 40, 54 aggregate information, 68, 71 CLI PE/COFF File Format, 56–58 GAC, 68 GUID heap, 66 metadata, 55, 56 module table, 65 MSIL directive, 56 MVID value, 66 package tab, 61 RVJ.Core, 56 version number parts, 60, 62–64 metadata system, 41 RVJ.Core.dll library, 37 static content, 38 .NET BCL Core library, 59 .NET Core BCL, 84 .NET core platform, 2, 4, 5

MSIL .property directive, 121, 122

R RVJ.Core.AssemblyInfoView interface, 71–75 RVJ.Core.DynamicField. DefineDynamicProperty() instance method, 147 RVJ.Core.DynamicProperty. DefineGetMethod() instance methods, 151 RVJ.Core.IDynamicAssembly. DefineDynamicModule() method, 86 RVJ.Core.IModuleInfoView interface, 69–71, 75–77 RVJ.Core .NET types flags and definition, 100, 101 purpose, 89, 90 RVJ.Core.IDynamicAssembly and Members, 93 RVJ.Core.IDynamicBuilder and Members, declaration, 91 RVJ.Core.IDynamicField Interface, 99 RVJ.Core.IDynamicModule Interface and Members, 94 RVJ.Core.IDynamicType Interface and Members, 96

181

INDEX

RVJ.Core .NET types (cont.) System.Reflection.Emit. AssemblyBuilder, 92–94 System.Reflection.Emit. FieldBuilder Class, 99, 100 System.Reflection.Emit. ModuleBuilder Class and Members, 95, 96 System.Reflection.Emit. TypeBuilder Class, 97, 98

S specialname/rtspecialname attributes CLR implementation, 125 ECMA-335 specification, 126 MSIL_Property.il, 127 .NET property, 126 attribute .other, 128–132 RVJ.core encapsulating functionality, 133–138 System.Reflection.AssemblyName instance, 104, 107 System.Reflection.Emit type AssemblyBuilderAccess Enum, 104–107 assembly type builder, 102, 103 builders, 102 dynamic .NET Type, definition, 108–110, 112

182

naming, 103, 104 System.Reflection.Emit. TypeBuilder. DefineProperty() instance method, 96, 97, 102, 108, 113–115, 143 System.Reflection. PropertyAttributes enum, 143, 147

T, U, V, W, X, Y, Z TFM list context menu, 23 forms designer, 27 IntelliSense, 26 Microsoft Visual Studio 2019 IntelliSense, 20 .NET Core, 14 netcore45, 16 netcore451, 16 netcoreapp, 15 .NET Framework, 14 netstandard, 16 .NET Standard, 14 NuGet repository, 28 RVJ.Core.csproj sample project file, 17, 19 source code, conditional symbols, 20 toolbox window, 24

INDEX Index

uap10.0, 16 universal windows platform, 15 WinForms designer, 29–32 WPF app, 21, 22 XAML file, 22 Tutorial, .NET Core

changing project config file, 10, 11 class library, 9, 10 .NET Standard, 12, 13 SDK, configuration, 8 target framework list, 10 using TFM, 12

183