{"id":328,"date":"2023-10-20T13:32:05","date_gmt":"2023-10-20T05:32:05","guid":{"rendered":"https:\/\/miie.net\/?p=328"},"modified":"2023-10-20T13:32:05","modified_gmt":"2023-10-20T05:32:05","slug":"pro-c10-chapter-16-building-and-configuring-class-libraries","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=328","title":{"rendered":"Pro C#10 CHAPTER 16 Building and Configuring Class Libraries"},"content":{"rendered":"<p>CHAPTER 16<\/p>\n<p>Building and Configuring Class Libraries<\/p>\n<p>For most of the examples so far in this book, you have created \u201cstand-alone\u201d executable applications, in which all the programming logic was packaged within a single assembly (<em>.dll) and executed using<br \/>\ndotnet.exe (or a copy of dotnet.exe named after the assembly). These assemblies were using little more<br \/>\nthan the .NET base class libraries. While some simple .NET programs may be constructed using nothing more than the base class libraries, chances are it will be commonplace for you (or your teammates) to isolate reusable programming logic into custom class libraries (<\/em>.dll files) that can be shared among applications.<br \/>\nIn this chapter, you\u2019ll start by learning the details of partitioning types into .NET namespaces. After this, you will take a deep look at class libraries in .NET, the difference between a .NET and .NET Standard, application configuration, publishing .NET console applications, and packaging your libraries into reusable NuGet packages.<\/p>\n<p>Defining Custom Namespaces (Updated 10.0)<br \/>\nBefore diving into the aspects of library deployment and configuration, the first task is to learn the details of packaging your custom types into .NET namespaces. Up to this point in the text, you\u2019ve been building<br \/>\nsmall test programs that leverage existing namespaces in the .NET universe (System, in particular). However, when you build larger applications with many types, it can be helpful to group your related types into custom namespaces. In C#, this is accomplished using the namespace keyword. Explicitly defining custom namespaces is even more important when creating shared assemblies, as other developers will need to reference the library and import your custom namespaces to use your types. Custom namespaces also prevent name collisions by segregating your custom classes from other custom classes that might have the same name.<br \/>\nTo investigate the issues firsthand, begin by creating a new .NET Console Application project named CustomNamespaces. Now, assume you are developing a collection of geometric classes named Square, Circle, and Hexagon. Given their similarities, you would like to group them into a unique namespace called CustomNamespaces.MyShapes within the CustomNamespaces.exe assembly.<\/p>\n<p>\u25a0 Guidance  While you are free to use any name you choose for your namespaces, the naming convention is typically similar to CompanyName.ProductName.AssemblyName.Path.<\/p>\n<p>\u00a9 Andrew Troelsen, Phil Japikse 2022<br \/>\nA. Troelsen and P. Japikse, Pro C# 10 with .NET 6, <a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_16\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_16\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_16\">https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_16<\/a><\/a><\/a><\/p>\n<p>625<\/p>\n<p>While the C# compiler has no problems with a single C# code file containing multiple types,<br \/>\nthis can be problematic when working in a team environment. If you are working on the Circle class and your co-worker needs to work on the Hexagon class, you would have to take turns working in the monolithic file or face difficult-to-resolve (well, at least time-consuming) merge conflicts.<br \/>\nA better approach is to place each class in its own file with a namespace definition. To ensure each type is packaged into the same logical group, simply wrap the given class definitions in the same namespace scope. The following example uses the pre\u2013C# 10 namespace syntax, where each namespace declaration wrapped its contents with an opening and closing curly brace, like so:<\/p>\n<p>\/\/ Circle.cs<br \/>\nnamespace CustomNamespaces.MyShapes<br \/>\n{<br \/>\n\/\/ Circle class<br \/>\npublic class Circle { \/<em> Interesting methods... <\/em>\/ }<br \/>\n}<br \/>\n\/\/ Hexagon.cs<br \/>\nnamespace CustomNamespaces.MyShapes<br \/>\n{<br \/>\n\/\/ Hexagon class<br \/>\npublic class Hexagon { \/<em> More interesting methods... <\/em>\/ }<br \/>\n}<br \/>\n\/\/ Square.cs<br \/>\nnamespace CustomNamespaces.MyShapes<br \/>\n{<br \/>\n\/\/ Square class<br \/>\npublic class Square { \/<em> Even more interesting methods...<\/em>\/}<br \/>\n}<\/p>\n<p>One of the updates in C# 10 is the addition of file-scoped namespaces. This eliminates the need for opening and closing curly braces wrapping the contents. Simply declare the namespace, and everything that comes after that namespace declaration is included in the namespace. The following code sample produces the same result as the example with the namespaces wrapping their contents:<\/p>\n<p>\/\/ Circle.cs<br \/>\nnamespace CustomNamespaces.MyShapes;<br \/>\n\/\/ Circle class<br \/>\npublic class Circle { \/<em> Interesting methods... <\/em>\/ }<br \/>\n\/\/ Hexagon.cs<br \/>\nnamespace CustomNamespaces.MyShapes;<br \/>\n\/\/ Hexagon class<br \/>\npublic class Hexagon { \/<em> More interesting methods... <\/em>\/ }<br \/>\n\/\/ Square.cs<br \/>\nnamespace CustomNamespaces.MyShapes;<br \/>\n\/\/ Square class<br \/>\npublic class Square { \/<em> Even more interesting methods...<\/em>\/}<\/p>\n<p>This change by the C# team (along with global using statements and implicit global using statements) has eliminated a lot of the boilerplate code that was necessary in versions of C# prior to C# 10. In fact, as I updated the book for this version, I was able to remove on average two pages per chapter, just by removing the (now) unnecessary using statements and curly braces!<\/p>\n<p>Notice how the CustomNamespaces.MyShapes namespace acts as the conceptual \u201ccontainer\u201d of these classes. When another namespace (such as CustomNamespaces) wants to use types in a separate namespace, you use the using keyword, just as you would when using namespaces of the .NET base class libraries, as follows:<\/p>\n<p>\/\/ Make use of types defined the MyShapes namespace. using CustomNamespaces.MyShapes;<\/p>\n<p>Hexagon h = new Hexagon(); Circle c = new Circle(); Square s = new Square();<\/p>\n<p>For this example, the assumption is that the C# files that define the CustomNamespaces.MyShapes namespace are part of the same Console Application project; in other words, all the files are compiled into a single assembly. If you defined the CustomNamespaces.MyShapes namespace within an external assembly, you would also need to add a reference to that library before you could compile successfully. You\u2019ll learn all the details of building applications that use external libraries during this chapter.<\/p>\n<p>Resolving Name Clashes with Fully Qualified Names<br \/>\nTechnically speaking, you are not required to use the C# using keyword when referring to types defined in external namespaces. You could use the fully qualified name of the type, which, as you may recall from Chapter 1, is the type\u2019s name prefixed with the defining namespace. Here\u2019s an example:<\/p>\n<p>\/\/ Note we are not importing CustomNamespaces.MyShapes anymore! CustomNamespaces.MyShapes.Hexagon h = new CustomNamespaces.MyShapes.Hexagon(); CustomNamespaces.MyShapes.Circle c = new CustomNamespaces.MyShapes.Circle(); CustomNamespaces.MyShapes.Square s = new CustomNamespaces.MyShapes.Square();<\/p>\n<p>Typically, there is no need to use a fully qualified name. Not only does it require a greater number of keystrokes, it also makes no difference whatsoever in terms of code size or execution speed. In fact, in CIL code, types are always defined with the fully qualified name. In this light, the C# using keyword is simply a typing time-saver.<br \/>\nHowever, fully qualified names can be helpful (and sometimes necessary) to avoid potential name clashes when using multiple namespaces that contain identically named types. Assume you have a new namespace termed CustomNamespaces.My3DShapes, which defines the following three classes, capable of rendering a shape in stunning 3D:<\/p>\n<p>\/\/ Another shape-centric namespace.<br \/>\n\/\/Circle.cs<br \/>\nnamespace CustomNamespaces.My3DShapes;<br \/>\n\/\/ 3D Circle class. public class Circle { }<br \/>\n\/\/Hexagon.cs<br \/>\nnamespace CustomNamespaces.My3DShapes;<br \/>\n\/\/ 3D Hexagon class. public class Hexagon { }<br \/>\n\/\/Square.cs<br \/>\nnamespace CustomNamespaces.My3DShapes;<br \/>\n\/\/ 3D Square class. public class Square { }<\/p>\n<p>If you update the top-level statements as shown next, you are issued several compile-time errors, because both namespaces define identically named classes:<\/p>\n<p>\/\/ Ambiguities abound!<br \/>\nusing CustomNamespaces.MyShapes; using CustomNamespaces.My3DShapes;<\/p>\n<p>\/\/ Which namespace do I reference?<br \/>\nHexagon h = new Hexagon(); \/\/ Compiler error! Circle c = new Circle(); \/\/ Compiler error! Square s = new Square();  \/\/ Compiler error!<\/p>\n<p>The ambiguity can be resolved using the type\u2019s fully qualified name, like so:<\/p>\n<p>\/\/ We have now resolved the ambiguity.<br \/>\nCustomNamespaces.My3DShapes.Hexagon h = new CustomNamespaces.My3DShapes.Hexagon(); CustomNamespaces.My3DShapes.Circle c = new CustomNamespaces.My3DShapes.Circle(); CustomNamespaces.MyShapes.Square s = new CustomNamespaces.MyShapes.Square();<\/p>\n<p>Resolving Name Clashes with Aliases<br \/>\nThe C# using keyword also lets you create an alias for a type\u2019s fully qualified name. When you do so, you define a token that is substituted for the type\u2019s full name at compile time. Defining aliases provides a second way to resolve name clashes. Here\u2019s an example:<\/p>\n<p>using MyShapes; using My3DShapes;<\/p>\n<p>\/\/ Resolve the ambiguity for a type using a custom alias. using The3DHexagon = My3DShapes.Hexagon;<\/p>\n<p>\/\/ This is really creating a My3DShapes.Hexagon class. The3DHexagon h2 = new The3DHexagon();<br \/>\n...<\/p>\n<p>There is another (more commonly used) using syntax that lets you create an alias for a namespace instead of a type. For example, you could alias the CustomNamespaces.My3DShapes namespace, and create an instance of the 3D Hexagon as follows:<\/p>\n<p>using ThreeD = CustomNamespaces.My3DShapes; ThreeD.Hexagon h2 = new ThreeD.Hexagon();<\/p>\n<p>\u25a0Note  Be aware that overuse of C# aliases for types can result in a confusing code base. if other programmers on your team are unaware of your custom aliases for types, they could have difficulty locating the real types in the project(s).<\/p>\n<p>Creating Nested Namespaces<br \/>\nWhen organizing your types, you are free to define namespaces within other namespaces. The base class libraries do so in numerous places to provide deeper levels of type organization. For example, the IO namespace is nested within System to yield System.IO. In fact, you already created nested namespaces in the previous example. The multipart namespaces (CustomNamespaces.MyShapes and CustomNamespaces. My3DShapes) are nested under the root namespace, CustomNamespaces.<br \/>\nAs you have seen already throughout this book, the .NET project templates add the initial code for console applications in a file named Program.cs. This file contains a namespace named after the project and a single class, Program. This base namespace is referred to as the root namespace. In our current example, the root namespace created by the .NET template is CustomNamespaces. To nest the MyShapes and My3DShapes namespaces inside the root namespace, there are three options. The first is to simply nest the namespace keyword, like this (using pre-C# 10 syntax):<\/p>\n<p>namespace CustomNamespaces<br \/>\n{<br \/>\nnamespace MyShapes<br \/>\n{<br \/>\n\/\/ Circle class public class Circle<br \/>\n{<br \/>\n\/<em> Interesting methods... <\/em>\/<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>The second option, using C# 10 (and later), uses a file-scoped namespace followed by a block-scoped namespace for the nested namespace:<\/p>\n<p>namespace CustomNamespaces; namespace MyShapes<br \/>\n{<br \/>\n\/\/ Circle class public class Circle<br \/>\n{<br \/>\n\/<em> Interesting methods... <\/em>\/<br \/>\n}<br \/>\n}<\/p>\n<p>The third option (and more commonly used) is to use \u201cdot notation\u201d in the namespace definition, as we did with the previous class examples:<\/p>\n<p>namespace CustomNamespaces.MyShapes;<br \/>\n\/\/ Circle class public class Circle<br \/>\n{<br \/>\n\/<em> Interesting methods... <\/em>\/<br \/>\n}<\/p>\n<p>Namespaces do not have to directly contain any types. This allows developers to use namespaces to provide a further level of scope.<\/p>\n<p>\u25a0Guidance a common practice is to group files in a namespace by directory. Where a file lives in the directory structure has no impact on namespaces. however, it does make the namespace structure clearer to other developers. therefore, many developers and code linting tools expect the namespaces to match the folder structures.<\/p>\n<p>Change the Root Namespace Using Visual Studio 2022<br \/>\nAs mentioned, when you create a new C# project using Visual Studio (or the .NET CLI), the name of your application\u2019s root namespace will be identical to the project name. From this point on, when you use Visual Studio to insert new code files using the Project \u27a4 Add New Item menu selection, types will automatically be wrapped within the root namespace and have directory path appended. If you want to change the name of the root namespace, simply access the \u201cDefault namespace\u201d option using the Application\/General tab of the project\u2019s properties window (see Figure 16-1).<\/p>\n<p>Figure 16-1. Configuring the default\/root namespace<\/p>\n<p>\u25a0Note the Visual studio property pages still refer to the root namespace as the Default namespace. You will see next why i refer to it as the root namespace.<\/p>\n<p>Change the Root Namespace Using the Project File<br \/>\nIf not using Visual Studio (or even with Visual Studio), you can also configure the root namespace by updating the project (*.csproj) file. With .NET projects, editing the project file in Visual Studio is as easy as double-clicking the project file in Solution Explorer (or right-clicking the project file in Solution Explorer<\/p>\n<p>and selecting \u201cEdit project file\u201d). Once the file is open, update the main PropertyGroup by adding the<br \/>\nRootNamespace node, like this:<\/p>\n<p><Project Sdk=\"Microsoft.NET.Sdk\"><\/p>\n<p><PropertyGroup><br \/>\n<OutputType>Exe<\/OutputType><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<ImplicitUsings>enable<\/ImplicitUsings><br \/>\n<Nullable>disable<\/Nullable><br \/>\n<RootNamespace>CustomNamespaces2<\/RootNamespace><br \/>\n<\/PropertyGroup><\/p>\n<p><\/Project><\/p>\n<p>So far, so good. Now that you have seen some details regarding how to package your custom types into well-organized namespaces, let\u2019s quickly review the benefits and format of the .NET assembly. After this, you will delve into the details of creating, deploying, and configuring your custom class libraries.<\/p>\n<p>The Role of .NET Assemblies<br \/>\n.NET applications are constructed by piecing together any number of assemblies. Simply put, an assembly is a versioned, self-describing binary file hosted by the .NET Runtime. Now, despite that .NET assemblies have the same file extensions (<em>.exe or <\/em>.dll) as previous Windows binaries, they have little in common under the hood with those files. Before unpacking that last statement, let\u2019s consider some of the benefits provided by the assembly format.<\/p>\n<p>Assemblies Promote Code Reuse<br \/>\nAs you have built your Console Application projects over the previous chapters, it might have seemed that all the applications\u2019 functionality was contained within the executable assembly you were constructing. Your applications were leveraging numerous types contained within the always-accessible .NET base class libraries.<br \/>\nAs you might know, a code library (also termed a class library) is a <em>.dll that contains types intended to be used by external applications. When you are creating executable assemblies, you will no doubt be leveraging numerous system-supplied and custom code libraries as you create your application. Do be aware, however, that a code library need not take a <\/em>.dll file extension. It is perfectly possible (although certainly not common) for an executable assembly to use types defined within an external executable file. In this light, a referenced *.exe can also be considered a code library.<br \/>\nRegardless of how a code library is packaged, the .NET platform allows you to reuse types in a language-independent manner. For example, you could create a code library in C# and reuse that library in any other .NET programming language. It is possible not only to allocate types across languages but also to derive from them. A base class defined in C# could be extended by a class authored in Visual Basic.<br \/>\nInterfaces defined in F# can be implemented by structures defined in C# and so forth. The point is that when you begin to break apart a single monolithic executable into numerous .NET assemblies, you achieve a language-neutral form of code reuse.<\/p>\n<p>Assemblies Establish a Type Boundary<br \/>\nRecall that a type\u2019s fully qualified name is composed by prefixing the type\u2019s namespace (e.g., System) to its name (e.g., Console). Strictly speaking, however, the assembly in which a type resides further<br \/>\nestablishes a type\u2019s identity. For example, if you have two uniquely named assemblies (say, MyCars.dll and YourCars.dll) that both define a namespace (CarLibrary) containing a class named SportsCar, they are considered unique types in the .NET universe.<\/p>\n<p>Assemblies Are Versionable Units<br \/>\n.NET assemblies are assigned a four-part numerical version number of the form <major>.<minor>.<build>.<\/p>\n<p><revision>. (If you do not explicitly provide a version number, the assembly is automatically assigned a version of 1.0.0.0, given the default .NET project settings.) This number allows multiple versions of the same assembly to coexist in harmony on a single machine.<\/p>\n<p>Assemblies Are Self-Describing<br \/>\nAssemblies are regarded as self-describing, in part because they record in the assembly\u2019s manifest every external assembly they must be able to access to function correctly. Recall from Chapter 1 that a manifest is a blob of metadata that describes the assembly itself (name, version, required external assemblies, etc.).<br \/>\nIn addition to manifest data, an assembly contains metadata that describes the composition (member names, implemented interfaces, base classes, constructors, etc.) of every contained type. Because an assembly is documented in such detail, the .NET Runtime does not consult the Windows system registry to resolve its location (quite the radical departure from Microsoft\u2019s legacy COM programming model). This separation from the registry is one of the factors that enables .NET applications to run on other operating systems besides Windows as well as supporting multiple versions of .NET on the same machine.<br \/>\nAs you will discover during this chapter, the .NET Runtime makes use of an entirely new scheme to resolve the location of external code libraries.<\/p>\n<p>Understanding the Format of a .NET Assembly<br \/>\nNow that you\u2019ve learned about several benefits provided by the .NET assembly, let\u2019s shift gears and get a better idea of how an assembly is composed under the hood. Structurally speaking, a .NET assembly (*.dll or *.exe) consists of the following elements:<br \/>\n\u2022An operating system (e.g., Windows) file header<br \/>\n\u2022A CLR file header<br \/>\n\u2022CIL code<br \/>\n\u2022Type metadata<br \/>\n\u2022An assembly manifest<br \/>\n\u2022Optional embedded resources<br \/>\nWhile the first two elements (the operating system and CLR headers) are blocks of data you can typically always ignore, they do deserve some brief consideration. Here\u2019s an overview of each element.<\/p>\n<p>Installing the C++ Profiling Tools<br \/>\nThe next few sections use a utility call dumpbin.exe, and it ships with the C++ profiling tools. To install them, type C++ profiling tools in the quick search bar, and click the prompt to install the tools, as shown in Figure 16-2.<\/p>\n<p>Figure 16-2. Installing the C++ profiling tools from Quick Launch<\/p>\n<p>This will bring up the Visual Studio installer with the tools selected. Alternatively, you can launch the Visual Studio installer yourself and select the components shown in Figure 16-3.<\/p>\n<p>Figure 16-3. Installing the C++ profiling tools<\/p>\n<p>The Operating System (Windows) File Header<br \/>\nThe operating system file header establishes the fact that the assembly can be loaded and manipulated by the target operating system (in the following example, Windows). This header data also identifies the kind of application (console-based, GUI-based, or *.dll code library) to be hosted by the operating system.<\/p>\n<p>Open the CarLibrary.dll file (in the book\u2019s repo or created later in this chapter) using the dumpbin.exe<br \/>\nutility (via the developer command prompt) with the \/headers flag as so:<br \/>\ndumpbin \/headers CarLibrary.dll<br \/>\nThis displays the assembly\u2019s operating system header information (shown in the following when built for Windows). Here is the (partial) Windows header information for CarLibrary.dll:<\/p>\n<p>Dump of file carlibrary.dll PE signature found<br \/>\nFile Type: DLL<\/p>\n<p>FILE HEADER VALUES<br \/>\n14C machine (x86)<br \/>\n3 number of sections 877429B3 time date stamp<br \/>\n0 file pointer to symbol table<br \/>\n0 number of symbols<br \/>\nE0 size of optional header 2022 characteristics<br \/>\nExecutable<br \/>\nApplication can handle large (>2GB) addresses DLL<br \/>\n...<\/p>\n<p>Now, remember that most .NET programmers will never need to concern themselves with the format of the header data embedded in a .NET assembly. Unless you happen to be building a new .NET language compiler (where you would care about such information), you are free to remain blissfully unaware of the grimy details of the header data. Do be aware, however, that this information is used under the covers when the operating system loads the binary image into memory.<\/p>\n<p>The CLR File Header<br \/>\nThe CLR header is a block of data that all .NET assemblies must support (and do support, courtesy of the C# compiler) to be hosted by the .NET Runtime. In a nutshell, this header defines numerous flags that enable the runtime to understand the layout of the managed file. For example, flags exist that identify the location of the metadata and resources within the file, the version of the runtime the assembly was built against, the value of the (optional) public key, and so forth. Execute dumpbin.exe again with the \/clrheader flag.<\/p>\n<p>dumpbin \/clrheader CarLibrary.dll<\/p>\n<p>You are presented with the internal CLR header information for a given .NET assembly, as shown here:<\/p>\n<p>Dump of file CarLibrary.dll File Type: DLL<\/p>\n<p>clr Header:<\/p>\n<p>48 cb<br \/>\n2.05 runtime version<\/p>\n<p>2158 [ B7C] RVA [size] of MetaData Directory<br \/>\n1 flags<br \/>\nIL Only<br \/>\n0 entry point token<br \/>\n0 [ 0] RVA [size] of Resources Directory<br \/>\n0 [ 0] RVA [size] of StrongNameSignature Directory<br \/>\n0 [ 0] RVA [size] of CodeManagerTable Directory<br \/>\n0 [ 0] RVA [size] of VTableFixups Directory<br \/>\n0 [ 0] RVA [size] of ExportAddressTableJumps Directory<br \/>\n0 [ 0] RVA [size] of ManagedNativeHeader Directory Summary<br \/>\n2000 .reloc<br \/>\n2000 .rsrc<br \/>\n2000 .text<\/p>\n<p>Again, as a .NET developer, you will not need to concern yourself with the gory details of an assembly\u2019s CLR header information. Just understand that every .NET assembly contains this data, which is used behind the scenes by the .NET Runtime as the image data loads into memory. Now turn your attention to some information that is much more useful in your day-to-day programming tasks.<\/p>\n<p>CIL Code, Type Metadata, and the Assembly Manifest<br \/>\nAt its core, an assembly contains CIL code, which, as you recall, is a platform- and CPU-agnostic intermediate language. At runtime, the internal CIL is compiled on the fly using a just-in-time (JIT) compiler, according to platform- and CPU-specific instructions. Given this design, .NET assemblies can indeed execute on a variety of architectures, devices, and operating systems. (Although you can live a happy and productive life without understanding the details of the CIL programming language, Chapter 18 offers an introduction to the syntax and semantics of CIL.)<br \/>\nAn assembly also contains metadata that completely describes the format of the contained types, as well as the format of external types referenced by this assembly. The .NET Runtime uses this metadata to resolve the location of types (and their members) within the binary, lay out types in memory, and facilitate remote method invocations. You\u2019ll check out the details of the .NET metadata format in Chapter 17 during your examination of reflection services.<br \/>\nAn assembly must also contain an associated manifest (also referred to as assembly metadata). The manifest documents each module within the assembly, establishes the version of the assembly, and documents any external assemblies referenced by the current assembly. As you will see over the course of this chapter, the CLR makes extensive use of an assembly\u2019s manifest during the process of locating external assembly references.<\/p>\n<p>Optional Assembly Resources<br \/>\nFinally, a .NET assembly may contain any number of embedded resources, such as application icons, image files, sound clips, or string tables. In fact, the .NET platform supports satellite assemblies that contain nothing but localized resources. This can be useful if you want to partition your resources based on a specific culture (English, German, etc.) for the purposes of building international software. The topic of building satellite assemblies is outside the scope of this text. Consult the .NET documentation for information on satellite assemblies and localization if you are interested.<\/p>\n<p>Class Libraries vs. Console Applications<br \/>\nSo far in this book, the examples were almost exclusively .NET Console applications. If you are reading this book as a current .NET Framework developer, these are like .NET console applications, with the main difference being the configuration process (to be covered later) and, of course, that they run on .NET Core. Console applications have a single-entry point (either a specified Main() method or top-level statements), can interact with the console, and can be launched directly from the operating system. Another difference<br \/>\nbetween .NET Framework and .NET console applications is that console applications in .NET are launched using the .NET Application Host (dotnet.exe).<br \/>\nClass libraries, on the other hand, don\u2019t have an entry point and therefore cannot be launched directly. They are used to encapsulate logic, custom types, and so on, and they are referenced by other class libraries and\/or console applications. In other words, class libraries are used to contain the things talked about in \u201cThe Role of .NET Assemblies\u201d section.<\/p>\n<p>.NET Standard vs. .NET (Core) Class Libraries<br \/>\n.NET (including .NET Core\/.NET 5\/.NET 6) class libraries run on .NET Core, and .NET Framework class libraries run on the .NET Framework. While it\u2019s pretty straightforward, there is a problem with this. Assume you have a large .NET Framework code base in your organization, with (potentially) years of development behind you and your team. There is probably a significant amount of shared code leveraged by the applications you and your team have built over the years. Perhaps it\u2019s centralized logging, reporting, or domain-specific functionality.<br \/>\nNow you (and your organization) want to move to the new .NET for all new development. What about all that shared code? The effort to rewrite all of your legacy code into .NET 6 assemblies could be significant, and until all of your applications were moved to .NET 6, you would have to support two versions (one in<br \/>\n.NET Framework and one in .NET 6). That would bring productivity to a screeching halt.<br \/>\nFortunately, the builders of .NET (Core) thought through this scenario. .NET Standard is a new type of class library project that was introduced with .NET Core 1.0 and can be referenced by .NET Framework as well as .NET (Core) applications. Before you get your hopes up, though, there is a catch with .NET 5 and<br \/>\n.NET 6. More on that shortly.<br \/>\nEach .NET Standard version defines a common set of APIs that must be supported by all .NET versions (.NET, .NET Core, Xamarin, etc.) to conform to the standard. For example, if you were to build a class library as a .NET Standard 2.0 project, it can be referenced by .NET 4.61+ and .NET Core 2.0+ (plus various versions of Xamarin, Mono, Universal Windows Platform, and Unity).<br \/>\nThis means you could move the code from your .NET Framework class libraries into .NET Standard 2.0 class libraries, and they can be shared by .NET (Core) and .NET Framework applications. That\u2019s much better than supporting duplicate copies of the same code, one for each framework.<br \/>\nNow for the catch. Each .NET Standard version represents the lowest common denominator for the frameworks that it supports. That means the lower the version, the less that you can do in your class library. While .NET Framework and .NET 6 projects can reference a .NET Standard 2.0 library, you cannot use a significant number of C# 8.0 features in a .NET Standard 2.0 library, and you can\u2019t use any new features from C# 9.0 or later. You must use .NET Standard 2.1 for full C# 8.0+ support. And .NET 4.8 (the latest\/last version of the original .NET Framework) only goes up to .NET Standard 2.0.<br \/>\nIt\u2019s still a good mechanism for leveraging existing code in newer applications up to and including .NET Core 3.1, but not a silver bullet. With the unification of the frameworks (.NET, Xamarin, Mon, etc.) with .NET 6, the usefulness of .NET Standard is slowly fading into the past.<\/p>\n<p>Configuring Applications with Configuration Files<br \/>\nWhile it is possible to keep all the information needed by your .NET application in the source code, being able to change certain values at runtime is vitally important in most applications of significance. One of the more common options is to use one or more configuration files shipped (or deployed) along with your application\u2019s executable.<br \/>\nThe .NET Framework relied mostly on XML files named app.config (or web.config for ASP.NET applications) for configuration. While XML-based configuration files can still be used, the most common method for configuring .NET applications is with JavaScript Object Notation (JSON) files..<\/p>\n<p>\u25a0Note  if you are not familiar with Json, it is a name-value pair format where each object is enclosed in curly braces. Values can also be objects using the same name-value pair format. Chapter 20 covers working with Json files in depth.<\/p>\n<p>To illustrate the process, create a new .NET Console application named FunWithConfiguration and add the following package reference to your project:<\/p>\n<p>dotnet new console -lang c# -n FunWithConfiguration -o .\\FunWithConfiguration -f net6.0 dotnet add FunWithConfiguration package Microsoft.Extensions.Configuration<br \/>\ndotnet add FunWithConfiguration package Microsoft.Extensions.Configuration.Binder dotnet add FunWithConfiguration package Microsoft.Extensions.Configuration.Json<\/p>\n<p>This adds a reference for configuration subsystem, the JSON file\u2013based .NET configuration subsystem, and the binding extensions for configuration into your project. Start by adding a new JSON file into your project named appsettings.json. Update the project file to make sure the file is always copied to the output directory when the project is built.<\/p>\n<p><ItemGroup><br \/>\n<None Update=\"appsettings.json\"><br \/>\n<CopyToOutputDirectory>Always<\/CopyToOutputDirectory><br \/>\n<\/None><br \/>\n<\/ItemGroup><\/p>\n<p>Finally, update the appsettings.json file to match the following:<\/p>\n<p>{<br \/>\n\"CarName\": \"Suzy\"<br \/>\n}<\/p>\n<p>The final step to adding the configuration into your app is to read in the configuration file and get the<br \/>\nCarName value. Update the Program.cs file to the following:<br \/>\nusing Microsoft.Extensions.Configuration; IConfiguration config = new ConfigurationBuilder()<br \/>\n.SetBasePath(Directory.GetCurrentDirectory())<br \/>\n.AddJsonFile(\"appsettings.json\", true, true)<br \/>\n.Build();<\/p>\n<p>The new configuration system starts with a ConfigurationBuilder. The path that the configuration build will start to look for the files being added is set with the SetBasePath() method. Then the configuration file is added with the AddJsonFile() method, which takes three parameters. The first parameter is the path and name of the file. Since this file is in the same location as the base path, there isn\u2019t any path information in the string, just the filename. The second parameter sets whether the file is optional (true) or required (false), and the final parameter determines if the configuration should use a file watcher to look for any changes in the file (true) or to ignore any changes during runtime (false). The final step<br \/>\nis to build the configuration into an instance of IConfiguration using the Build() method. This instance provides access to all the configured values.<\/p>\n<p>\u25a0 Note  file watchers are covered in Chapter 20.<\/p>\n<p>Once you have an instance of IConfiguration, you can get the values from the configuration files much the same way as calling the ConfigurationManager in .NET 4.8. Add the following to the bottom of the Main() method, and when you run the app, you will see the value written to the console:<\/p>\n<p>Console.WriteLine($\"My car's name is {config[\"CarName\"]}\");<\/p>\n<p>If the request name doesn\u2019t exist in the configuration, the result will be null. The following code still runs without an exception; it just doesn\u2019t display a name in the first line and displays True in the second line:<\/p>\n<p>Console.WriteLine($\"My car's name is {config[\"CarName2\"]}\"); Console.WriteLine($\"CarName2 is null? {config[\"CarName2\"] == null}\");<\/p>\n<p>There is also a GetValue() method (and its generic version GetValue<T>()) that can retrieve primitive values from the configuration. The following example shows both of these methods to get the CarName:<\/p>\n<p>Console.WriteLine($\"My car's name is {config.GetValue(typeof(string),\"CarName\")}\"); Console.WriteLine($\"My car's name is {config.GetValue<string>(\"CarName\")}\");<\/p>\n<p>These methods return the default value (e.g., null for reference types, 0 for numeric types) if the name requested doesn\u2019t exist. The following code returns 0 for the CarName2 property:<\/p>\n<p>Console.WriteLine($\"My car's name is {config.GetValue<int>(\"CarName2\")}\");<\/p>\n<p>These methods will throw an exception if the value found for the name can\u2019t be intrinsically cast to the requested datatype. For example, trying to cast the CarName property to an int will throw an InvalidOperationException as shown by the following code:<\/p>\n<p>try<br \/>\n{<br \/>\nConsole.WriteLine($\"My car's name is {config.GetValue<int>(\"CarName\")}\");<br \/>\n}<br \/>\ncatch (InvalidOperationException ex)<br \/>\n{<br \/>\nConsole.WriteLine($\"An exception occurred: {ex.Message}\");<br \/>\n}<\/p>\n<p>\u25a0 Note  the GetValue() method is designed to work with primitive types. for complex types, use the<br \/>\nBind() or Get()\/Get<T>() methods, covered in the \u201cWorking with objects\u201d section.<\/p>\n<p>Multiple Configuration Files<br \/>\nMore than one configuration file can be added into the configuration system. When more than one file is used, the configuration properties are additive, unless any names of the name-value pairs conflict. When there is a name conflict, the last one in wins. To see this in action, add another file named appsettings. development.json, and set the project to always copy it to the output directory.<\/p>\n<p><ItemGroup><br \/>\n<None Update=\"appsettings.development.json\"><br \/>\n<CopyToOutputDirectory>Always<\/CopyToOutputDirectory><br \/>\n<\/None><br \/>\n<\/ItemGroup><\/p>\n<p>Update the JSON to the following:<\/p>\n<p>{<br \/>\n\"CarName\": \"Logan\"<br \/>\n}<\/p>\n<p>Now update the code that creates the instance of the IConfiguration interface to the following (update in bold):<\/p>\n<p>IConfiguration config = new ConfigurationBuilder()<br \/>\n.SetBasePath(Directory.GetCurrentDirectory())<br \/>\n.AddJsonFile(\"appsettings.json\", true, true)<br \/>\n.AddJsonFile(\"appsettings.development.json\", true, true)<br \/>\n.Build();<\/p>\n<p>When you run this program, you will see the name of the car is indeed Logan, and not Suzy.<\/p>\n<p>Working with Objects (Updated 10.0)<br \/>\nOur sample JSON file so far is extremely simple with a single name-value pair. In real-world projects, application configuration is usually more complex than a single property. Update the appsettings. development.json file to the following, which adds a new Car object to the JSON:<\/p>\n<p>{<br \/>\n\"CarName\": \"Suzy\",<br \/>\n\"Car\": {<br \/>\n\"Make\":\"Honda\",<br \/>\n\"Color\": \"Blue\", \"PetName\":\"Dad's Taxi\"<br \/>\n}<br \/>\n}<\/p>\n<p>To access multilevel JSON values, the key used for searching is the hierarchy of the JSON, with each level separated by colons (:). For example, the key for the Car object\u2019s Make property is Car:Make. Update the top-level statements to the following to get all the Car properties and display them:<\/p>\n<p>Console.Write($\"My car object is a {config[\"Car:Color\"]} \"); Console.WriteLine($\"{config[\"Car:Make\"]} named {config[\"Car:PetName\"]}\");<\/p>\n<p>Instead of traversing the hierarchy of names, entire sections can be retrieved using the GetSection() method. Once you have the section, you can then get the values from the section using the simple name format, as shown in the following example:<\/p>\n<p>IConfigurationSection section = config.GetSection(\"Car\"); Console.Write($\"My car object is a {section[\"Color\"]} \"); Console.WriteLine($\"{section[\"Make\"]} named {section[\"PetName\"]}\");<\/p>\n<p>As a final note for working with objects, you can use the Bind() method to bind configuration values to an existing instance of an object or the Get() method to create a new object instance. These are similar to the GetValue() method but work with nonprimitive types. To get started, create a simple Car class at the end of the top-level statements:<\/p>\n<p>public class Car<br \/>\n{<br \/>\npublic string Make {get;set;} public string Color {get;set;} public string PetName { get; set; }<br \/>\n}<\/p>\n<p>Next, create a new instance of a Car class, and then call Bind() on the section passing in the Car instance:<\/p>\n<p>var c = new Car(); section.Bind(c);<br \/>\nConsole.Write($\"My car object is a {c.Color} \"); Console.WriteLine($\"{c.Make} named {c.PetName}\");<\/p>\n<p>If the section isn\u2019t configured, the Bind() method will not update the instance but will leave all of the properties as they existed prior to the call to Bind(). The following will leave the Color set to Red and the remaining properties null:<\/p>\n<p>var notFoundCar = new Car { Color = \"Red\"}; config.GetSection(\"Car2\").Bind(notFoundCar); Console.Write($\"My car object is a {notFoundCar.Color} \");<br \/>\nConsole.WriteLine($\"{notFoundCar.Make} named {notFoundCar.PetName}\");<\/p>\n<p>The Get() method creates a new instance of the specified type from a section of the configuration. The non-generic version of the method returns an object type, so the return value must be cast to the specific type before being used. Here is an example using the Get() method to create an instance of the Car class from the Car section of the configuration:<\/p>\n<p>var carFromGet = config.GetSection(nameof(Car)).Get(typeof(Car)) as Car; Console.Write($\"My car object (using Get()) is a {carFromGet.Color} \"); Console.WriteLine($\"{carFromGet.Make} named {carFromGet.PetName}\");<\/p>\n<p>If the named section isn\u2019t found, the Get() method returns null:<\/p>\n<p>var notFoundCarFromGet = config.GetSection(\"Car2\").Get(typeof(Car)); Console.WriteLine($\"The not found car is null? {notFoundCarFromGet == null}\");<\/p>\n<p>The generic version returns an instance of the specified type without having to perform a cast. If the section isn\u2019t found, the method returns null.<\/p>\n<p>\/\/Returns a Car instance<br \/>\nvar carFromGet2 = config.GetSection(nameof(Car)).Get<Car>(); Console.Write($\"My car object (using Get()) is a {carFromGet.Color} \"); Console.WriteLine($\"{carFromGet.Make} named {carFromGet.PetName}\");<\/p>\n<p>\/\/Returns null<br \/>\nvar notFoundCarFromGet2 = config.GetSection(\"Car2\").Get<Car>(); Console.WriteLine($\"The not found car is null? {notFoundCarFromGet2 == null}\");<\/p>\n<p>The Bind() and Get()\/Get<T>() methods use reflection (covered in the next chapter) to match the names of the public properties on the class to the names in the configuration section in a case-insensitive manner. For example, if you update appsettings.development.json to the following (notice the casing change of the petName property), the previous code will still work:<\/p>\n<p>{<br \/>\n\"CarName\": \"Suzy\", \"Car\": {<br \/>\n\"Make\":\"Honda\",<br \/>\n\"Color\": \"Blue\",<br \/>\n\"petName\":\"Dad's Taxi\"<br \/>\n}<br \/>\n}<\/p>\n<p>If a property in the configuration doesn\u2019t exist in the class (or the name is spelled differently), then that particular configuration value (by default) is ignored. If you update the JSON to the following, the Make and Color properties are populated, but the PetName property on the Car object isn\u2019t:<\/p>\n<p>{<br \/>\n\"CarName\": \"Suzy\", \"Car\": {<br \/>\n\"Make\":\"Honda\",<br \/>\n\"Color\": \"Blue\",<br \/>\n\"PetNameForCar\":\"Dad's Taxi\"<br \/>\n}<br \/>\n}<\/p>\n<p>The Bind(), Get() and Get<T>() methods can optionally take an Action<BinderOptions> to further refine the process of updating (Bind()) or instantiating (Get()\/Get<T>()) class instances. The BinderOptions class is listed here:<\/p>\n<p>public class BinderOptions<br \/>\n{<br \/>\npublic bool BindNonPublicProperties { get; set; } \/\/Defaults to false public bool ErrorOnUnknownConfiguration { get; set; } \/\/Defaults to false<br \/>\n}<\/p>\n<p>If the ErrorOnUnknownConfiguration option is set to true, then an InvalidOperationException will be thrown if the configuration contains a name that doesn\u2019t exist on the model. With the renamed configuration value (PetNameForCar), the following call throws the exception listed in the code sample:<\/p>\n<p>try<br \/>\n{<br \/>\n_ = config.GetSection(nameof(Car)).Get<Car>(t=>t.ErrorOnUnknownConfiguration=true);<br \/>\n}<br \/>\ncatch (InvalidOperationException ex)<br \/>\n{<br \/>\nConsole.WriteLine($\"An exception occurred: {ex.Message}\");<br \/>\n}<br \/>\n\/*<br \/>\nError message: 'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of Car: 'PetNameForCar'<br \/>\n*\/<\/p>\n<p>The other option allows for binding non-public properties. By default, both properties are false. If non- public properties should be bound from the configuration, set the BindNonPublicProperties, like this:<\/p>\n<p>var carFromGet3 = config.GetSection(nameof(Car)).Get<Car>(t=>t.BindNonPublicPropert ies=true);<\/p>\n<p>New in C# 10, the GetRequiredSection() method will throw an exception if the section isn\u2019t configured. For example, the following code will throw an exception since there isn\u2019t a Car2 section in the configuration:<\/p>\n<p>try<br \/>\n{<br \/>\nconfig.GetRequiredSection(\"Car2\").Bind(notFoundCar);<br \/>\n}<br \/>\ncatch (InvalidOperationException ex)<br \/>\n{<br \/>\nConsole.WriteLine($\"An exception occurred: {ex.Message}\");<br \/>\n}<\/p>\n<p>Additional Configuration Options<br \/>\nIn addition to using file-based configuration, there are options to use environment variables, Azure Key Vault, command-line arguments, and many more. Many of these are used intrinsically in ASP.NET Core, as you will see later in this book. You can also reference the .NET documentation for more information on using other methods for configuring applications.<\/p>\n<p>Building and Consuming a .NET Class Library<br \/>\nTo begin exploring the world of .NET class libraries, you\u2019ll first create a *.dll assembly (named CarLibrary) that contains a small set of public types. If you are working in Visual Studio, name the solution file something meaningful (different than CarLibrary). You will add two projects into the solution later in this section. If you are working in Visual Studio Code, you don\u2019t need to have a solution, although most developers find it useful to group related projects into a single solution.<br \/>\nAs a reminder, you can create and manage solutions and projects through the .NET Command Line Interface (CLI). Use the following command to create the solution and class library:<\/p>\n<p>dotnet new sln -n Chapter16_AllProjects<br \/>\ndotnet new classlib -lang c# -n CarLibrary -o .\\CarLibrary -f net6.0 dotnet sln .\\Chapter16_AllProjects.sln add .\\CarLibrary<\/p>\n<p>The first command creates an empty solution file named Chapter16_AllProjects (-n) in the current directory. The next command creates a new .NET 6.0 (-f) class library named CarLibrary (-n) in the subdirectory named CarLibrary (-o). The output (-o) location is optional. If left off, the project will be created in a subdirectory with the same name as the project name. The final command adds the new project to the solution.<\/p>\n<p>\u25a0 Note the .net Cli has a good help system. to get help for any command, add -h to the command. for example, to see all templates, type dotnet new -h. to get more information about creating a class library, type dotnet new classlib -h.<\/p>\n<p>Now that you have created your project and solution, you can open it in Visual Studio (or Visual Studio Code) to begin building the classes. After opening the solution, delete the autogenerated file Class1.cs.<br \/>\nThe design of your automobile library begins with the EngineStateEnum and MusicMediaEnum enums. Add two files to your project named MusicMediaEnum.cs and EngineStateEnum.cs and add the following code to each file, respectively:<\/p>\n<p>\/\/MusicMediaEnum.cs namespace CarLibrary;<br \/>\n\/\/ Which type of music player does this car have? public enum MusicMediaEnum<br \/>\n{<br \/>\nMusicCd, MusicTape, MusicRadio, MusicMp3<br \/>\n}<br \/>\n\/\/EngineStateEnum.cs namespace CarLibrary;<br \/>\n\/\/ Represents the state of the engine. public enum EngineStateEnum<br \/>\n{<br \/>\nEngineAlive, EngineDead<br \/>\n}<\/p>\n<p>Next, insert a new C# class file into your project, named Car.cs, that will hold an abstract base class named Car. This class defines various state data via automatic property syntax. This class also has a single abstract method named TurboBoost(), which uses the EngineStateEnum enumeration to represent the current condition of the car\u2019s engine. Update the file to the following code:<\/p>\n<p>namespace CarLibrary;<br \/>\n\/\/ The abstract base class in the hierarchy. public abstract class Car<br \/>\n{<br \/>\npublic string PetName {get; set;} public int CurrentSpeed {get; set;} public int MaxSpeed {get; set;}<\/p>\n<p>protected EngineStateEnum State = EngineStateEnum.EngineAlive; public EngineStateEnum EngineState => State;<br \/>\npublic abstract void TurboBoost();<\/p>\n<p>protected Car(){}<br \/>\nprotected Car(string name, int maxSpeed, int currentSpeed)<br \/>\n{<br \/>\nPetName = name;<br \/>\nMaxSpeed = maxSpeed;<br \/>\nCurrentSpeed = currentSpeed;<br \/>\n}<br \/>\n}<\/p>\n<p>Now assume you have two direct descendants of the Car type named MiniVan and SportsCar. Each overrides the abstract TurboBoost() method by displaying an appropriate message via console message. Insert two new C# class files into your project, named MiniVan.cs and SportsCar.cs, respectively. Update the code in each file with the relevant code.<\/p>\n<p>\/\/SportsCar.cs namespace CarLibrary;<br \/>\npublic class SportsCar : Car<br \/>\n{<br \/>\npublic SportsCar(){ } public SportsCar(<br \/>\nstring name, int maxSpeed, int currentSpeed)<br \/>\n: base (name, maxSpeed, currentSpeed){ }<\/p>\n<p>public override void TurboBoost()<br \/>\n{<br \/>\nConsole.WriteLine(\"Ramming speed! Faster is better...\");<br \/>\n}<br \/>\n}<\/p>\n<p>\/\/MiniVan.cs namespace CarLibrary;<br \/>\npublic class MiniVan : Car<br \/>\n{<br \/>\npublic MiniVan(){ }<\/p>\n<p>public MiniVan(<br \/>\nstring name, int maxSpeed, int currentSpeed)<br \/>\n: base (name, maxSpeed, currentSpeed){ }<\/p>\n<p>public override void TurboBoost()<br \/>\n{<br \/>\n\/\/ Minivans have poor turbo capabilities! State = EngineStateEnum.EngineDead;<br \/>\nConsole.WriteLine(\"Eek! Your engine block exploded!\");<br \/>\n}<br \/>\n}<\/p>\n<p>Exploring the Manifest<br \/>\nBefore using CarLibrary.dll from a client application, let\u2019s check out how the code library is composed under the hood. Assuming you have compiled this project, run ildasm.exe against the compiled assembly. If you don\u2019t have ildasm.exe (covered earlier in this book), it is also located in the Chapter 16 directory of this book\u2019s repository.<\/p>\n<p>ildasm \/METADATA \/out=CarLibrary.il .\\CarLibrary\\bin\\Debug\\net6.0\\CarLibrary.dll<\/p>\n<p>The Manifest section of the disassembled results starts with \/\/Metadata version: 4.0.30319. Immediately following is the list of all external assemblies required by the class library, as shown here:<\/p>\n<p>\/\/ Metadata version: v4.0.30319<br \/>\n.assembly extern System.Runtime<br \/>\n{<br \/>\n.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )<br \/>\n.ver 6:0:0:0<br \/>\n}<br \/>\n.assembly extern System.Console<br \/>\n{<br \/>\n.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )<br \/>\n.ver 6:0:0:0<br \/>\n}<\/p>\n<p>Each .assembly extern block is qualified by the .publickeytoken and .ver directives. The<br \/>\n.publickeytoken instruction is present only if the assembly has been configured with a strong name. The<br \/>\n.ver token defines the numerical version identifier of the referenced assembly.<\/p>\n<p>\u25a0 Note  prior versions of the .net framework relied heavily on strong naming, which involved using a public\/ private key combination. this was required on Windows for an assembly to be added into the global assembly Cache, but its need has severely diminished with the advent of .net Core.<\/p>\n<p>After the external references, you will find a number of .custom tokens that identify assembly-level attributes (some system generated, but also copyright information, company name, assembly version, etc.). Here is a (very) partial listing of this chunk of manifest data:<\/p>\n<p>.assembly CarLibrary<br \/>\n{<br \/>\n...<br \/>\n.custom instance void ... TargetFrameworkAttribute ...<br \/>\n.custom instance void ... AssemblyCompanyAttribute ...<br \/>\n.custom instance void ... AssemblyConfigurationAttribute ...<br \/>\n.custom instance void ... AssemblyFileVersionAttribute ...<br \/>\n.custom instance void ... AssemblyProductAttribute ...<br \/>\n.custom instance void ... AssemblyTitleAttribute ...<\/p>\n<p>These settings can be set either using the Visual Studio property pages or editing the project file and adding in the correct elements. To edit the package properties in Visual Studio 2022, right-click the project in Solution Explorer, select Properties, and navigate to the Package menu in the left rail of the window. This brings up the dialog shown in Figure 16-4. For the sake of brevity, Figure 16-4 is only part of the dialog.<\/p>\n<p>Figure 16-4. Editing assembly information using Visual Studio 2022\u2019s Properties window<\/p>\n<p>\u25a0 Note there are three different version fields in the package screen. the assembly version and the file version use the same schema, which is based on semantic versioning (https:\/\/semver.org). the first number is the major build version, the second is the minor build version, and the third is the patch number. the fourth number is usually used to indicate a build number. the package version number should adhere to semantic versioning, with just the {major}.{minor}.{patch} placeholders used. semantic versioning allows for an alphanumeric extension to the version, which is separated by a dash instead of a period (e.g., 1.0.0-rc). this denotes noncomplete versions, such as betas and release candidates.<\/p>\n<p>a package version sets the nuget package version (nuget packaging is covered in more detail later in this chapter). the assembly version is used by .net during build and runtime to locate, link, and load assemblies. the file version is used only by Windows explorer, and it not used by .net.<\/p>\n<p>Another way to add the metadata to your assembly is directly in the *.csproj project file. The following update to the main PropertyGroup in the project file does the same thing as filling in the form shown in Figure 16-4. Notice that the package version is simply called Version in the project file.<\/p>\n<p><PropertyGroup><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<ImplicitUsings>enable<\/ImplicitUsings><br \/>\n<Nullable>disable<\/Nullable><br \/>\n<Copyright>Copyright 2021<\/Copyright><br \/>\n<Authors>Phil Japikse<\/Authors><br \/>\n<Company>Apress<\/Company><br \/>\n<Product>Pro C# 10.0<\/Product><br \/>\n<PackageId>CarLibrary<\/PackageId><br \/>\n<Description>This is an awesome library for cars.<\/Description><br \/>\n<AssemblyVersion>1.0.0.1<\/AssemblyVersion><br \/>\n<FileVersion>1.0.0.2<\/FileVersion><br \/>\n<Version>1.0.3<\/Version><br \/>\n<\/PropertyGroup><\/p>\n<p>\u25a0 Note the rest of the entries from figure 16-4 (and the project file listing) are used when generating nuget packages from your assembly. this is covered later in the chapter.<\/p>\n<p>Exploring the CIL<br \/>\nRecall that an assembly does not contain platform-specific instructions; rather, it contains platform-agnostic Common Intermediate Language (CIL) instructions. When the .NET Runtime loads an assembly into memory, the underlying CIL is compiled (using the JIT compiler) into instructions that can be understood by the target platform. For example, the TurboBoost() method of the SportsCar class is represented by the following CIL:<\/p>\n<p>.method public hidebysig virtual<br \/>\ninstance void TurboBoost() cil managed<br \/>\n{<br \/>\n.maxstack 8 IL_0000: nop<br \/>\nIL_0001: ldstr \"Ramming speed! Faster is better...\"<br \/>\nIL_0006: call void [System.Console]System.Console::WriteLine(string) IL_000b: nop<br \/>\nIL_000c: ret<br \/>\n}<br \/>\n\/\/ end of method SportsCar::TurboBoost<\/p>\n<p>As with the other CIL examples in this book, most .NET developers don\u2019t need to be deeply concerned with the details. Chapter 18 provides more details on its syntax and semantics, which can be helpful when you are building more complex applications that require advanced services, such as runtime construction of assemblies.<\/p>\n<p>Exploring the Type Metadata<br \/>\nBefore you build some applications that use your custom .NET library, examine the metadata for the types within the CarLibrary.dll assembly. For an example, here is the TypeDef for the EngineStateEnum:<\/p>\n<p>TypeDef #2<\/p>\n<p>TypDefName: CarLibrary.EngineStateEnum<br \/>\nFlags   : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] Extends    : [TypeRef] System.Enum<br \/>\nField #1<\/p>\n<p>Field Name: value<br \/>\nFlags   : [Public] [SpecialName] [RTSpecialName] CallCnvntn: [FIELD]<br \/>\nField type: I4 Field #2<br \/>\nField Name: EngineAlive<br \/>\nFlags   : [Public] [Static] [Literal] [HasDefault] DefltValue: (I4) 0<br \/>\nCallCnvntn: [FIELD]<br \/>\nField type: ValueClass CarLibrary.EngineStateEnum Field #3<br \/>\nField Name: EngineDead<br \/>\nFlags   : [Public] [Static] [Literal] [HasDefault] DefltValue: (I4) 1<br \/>\nCallCnvntn: [FIELD]<br \/>\nField type: ValueClass CarLibrary.EngineStateEnum<\/p>\n<p>As explained in the next chapter, an assembly\u2019s metadata is an important element of the .NET platform and serves as the backbone for numerous technologies (object serialization, late binding, extendable applications, etc.). In any case, now that you have looked inside the CarLibrary.dll assembly, you can build some client applications that use your types.<\/p>\n<p>Building a C# Client Application<br \/>\nBecause each of the CarLibrary project types has been declared using the public keyword, other .NET applications are able to use them as well. Recall that you may also define types using the C# internal keyword (in fact, this is the default C# access mode for classes). Internal types can be used only by the assembly in which they are defined. External clients can neither see nor create types marked with the internal keyword.<\/p>\n<p>\u25a0 Note the exception to the internal rule is when an assembly explicitly allows access to another assembly using the InternalsVisibleTo attribute, covered shortly.<\/p>\n<p>To use your library\u2019s functionality, create a new C# Console Application project named CSharpCarClient in the same solution as CarLibrary. You can do this using Visual Studio (right-click the solution and select Add \u27a4 New Project) or using the command line (three lines, each executed separately).<\/p>\n<p>dotnet new console -lang c# -n CSharpCarClient -o .\\CSharpCarClient -f net6.0 dotnet add CSharpCarClient reference CarLibrary<br \/>\ndotnet sln .\\Chapter16_AppRojects.sln add .\\CSharpCarClient<\/p>\n<p>The previous commands created the console application, added a project reference to the CarLibrary project for the new project, and added it to your solution.<\/p>\n<p>\u25a0 Note the add reference command creates a project reference. this is convenient for development, as CsharpCarClient will always be using the latest version of Carlibrary. You can also reference an assembly directly. direct references are created by referencing the compiled class library.<\/p>\n<p>If you still have the solution open in Visual Studio, you\u2019ll notice that the new project shows up in Solution Explorer without any intervention on your part.<br \/>\nThe final change to make is to right-click CSharpCarClient in Solution Explorer and select \u201cSet as Startup Project.\u201d If you are not using Visual Studio, you can run the new project by executing dotnet run in the project directory.<\/p>\n<p>\u25a0 Note  You can set the project reference in Visual studio as well by right-clicking the CsharpCarClient project in solution explorer, selecting add \u27a4 reference, and selecting the Carlibrary project from the project\u2019s node.<\/p>\n<p>At this point, you can build your client application to use the external types. Update the Program.cs file as follows:<\/p>\n<p>\/\/ Don't forget to import the CarLibrary namespace! using CarLibrary;<\/p>\n<p>Console.WriteLine(\"***** C# CarLibrary Client App *****\");<br \/>\n\/\/ Make a sports car.<br \/>\nSportsCar viper = new SportsCar(\"Viper\", 240, 40); viper.TurboBoost();<\/p>\n<p>\/\/ Make a minivan.<br \/>\nMiniVan mv = new MiniVan(); mv.TurboBoost();<\/p>\n<p>Console.WriteLine(\"Done. Press any key to terminate\"); Console.ReadLine();<\/p>\n<p>This code looks just like the code of the other applications developed thus far in the book. The only point of interest is that the C# client application is now using types defined within a separate custom library. Run your program and verify that you see the display of messages.<br \/>\nYou might be wondering exactly what happened when you referenced the CarLibrary project. When a project reference is made, the solution build order is adjusted so that dependent projects (CarLibrary in this example) build first, and then the output from that build is copied into the output directory of the parent project (CSharpCarLibrary). The compiled client library references the compiled class library. When the client project is rebuilt, so is the dependent library, and the new version is once again copied to the target folder.<\/p>\n<p>\u25a0 Note  if you are using Visual studio, you can click the show all files button in solution explorer, and you can see all the output files and verify that the compiled Carlibrary is there. if you are using Visual studio Code, navigate to the bin\/debug\/net6.0 directory in the explorer tab.<\/p>\n<p>When a direct reference instead of a project reference is made, the compiled library is also copied to the output directory of the client library, but at the time the reference is made. Without the project reference in place, the projects can be built independently of each other, and the files could become out of sync. In short, if you are developing dependent libraries (as is usually the case with real software projects), it is best to reference the project and not the project output.<\/p>\n<p>Building a Visual Basic Client Application<br \/>\nRecall that the .NET platform allows developers to share compiled code across programming languages. To illustrate the language-agnostic attitude of the .NET platform, let\u2019s create another Console Application project (VisualBasicCarClient), this time using Visual Basic (note that each of commands is a one-line command).<\/p>\n<p>dotnet new console -lang vb -n VisualBasicCarClient -o .\\VisualBasicCarClient -f net6.0 dotnet add VisualBasicCarClient reference CarLibrary<br \/>\ndotnet sln .\\Chapter16_AllProjects.sln add VisualBasicCarClient<\/p>\n<p>Like C#, Visual Basic allows you to list each namespace used within the current file. However, Visual Basic uses the Imports keyword rather than the C# using keyword, so add the following Imports statement within the Program.vb code file:<\/p>\n<p>Imports CarLibrary<br \/>\nModule Program Sub Main() End Sub<br \/>\nEnd Module<\/p>\n<p>Notice that the Main() method is defined within a Visual Basic module type. In a nutshell, modules are a Visual Basic notation for defining a class that can contain only static methods (much like a C# static<br \/>\nclass). In any case, to exercise the MiniVan and SportsCar types using the syntax of Visual Basic, update your<br \/>\nMain() method as follows:<\/p>\n<p>Sub Main()<br \/>\nConsole.WriteLine(\"***** VB CarLibrary Client App *****\")<br \/>\n' Local variables are declared using the Dim keyword.<\/p>\n<p>Dim myMiniVan As New MiniVan() myMiniVan.TurboBoost()<\/p>\n<p>Dim mySportsCar As New SportsCar() mySportsCar.TurboBoost() Console.ReadLine()<br \/>\nEnd Sub<\/p>\n<p>When you compile and run your application (make sure to set VisualBasicCarClient as the startup project if you are using Visual Studio), you will once again find a series of message boxes displayed.<br \/>\nFurthermore, this new client application has its own local copy of CarLibrary.dll located under the bin\\ Debug\\net6.0 folder.<\/p>\n<p>Cross-Language Inheritance in Action<br \/>\nAn enticing aspect of .NET development is the notion of cross-language inheritance. To illustrate, let\u2019s create a new Visual Basic class that derives from SportsCar (which was authored using C#). First, add a new class file named PerformanceCar.vb to your current Visual Basic application. Update the initial class definition by deriving from the SportsCar type using the Inherits keyword. Then, override the abstract TurboBoost() method using the Overrides keyword, like so:<\/p>\n<p>Imports CarLibrary<br \/>\n' This VB class is deriving from the C# SportsCar.<br \/>\nPublic Class PerformanceCar Inherits SportsCar<br \/>\nPublic Overrides Sub TurboBoost()<br \/>\nConsole.WriteLine(\"Zero to 60 in a cool 4.8 seconds...\") End Sub<br \/>\nEnd Class<\/p>\n<p>To test this new class type, update the module\u2019s Main() method as follows:<\/p>\n<p>Sub Main()<br \/>\n...<br \/>\nDim dreamCar As New PerformanceCar()<\/p>\n<p>' Use Inherited property. dreamCar.PetName = \"Hank\" dreamCar.TurboBoost() Console.ReadLine()<br \/>\nEnd Sub<\/p>\n<p>Notice that the dreamCar object can invoke any public member (such as the PetName property) found up the chain of inheritance, although the base class was defined in a completely different language and in a completely different assembly! The ability to extend classes across assembly boundaries in a language- independent manner is a natural aspect of the .NET development cycle. This makes it easy to use compiled code written by individuals who would rather not build their shared code with C#.<\/p>\n<p>Exposing internal Types to Other Assemblies<br \/>\nAs mentioned earlier, internal classes are visible only to other objects in the assembly where they are defined. The exception to this is when visibility is explicitly granted to another project.<br \/>\nBegin by adding a new class named MyInternalClass to the CarLibrary project, and update the code to the following:<\/p>\n<p>namespace CarLibrary;<br \/>\ninternal class MyInternalClass<br \/>\n{<br \/>\n}<\/p>\n<p>\u25a0 Note Why expose internal types at all? this is usually done for unit and integration testing. developers want to be able to test their code but not necessarily expose it beyond the borders of the assembly.<\/p>\n<p>Using an Assembly Attribute<br \/>\nChapter 17 will cover attributes in depth, but for now open the Car.cs class in the CarLibrary project, and add the following attribute and using statement:<\/p>\n<p>using System.Runtime.CompilerServices; [assembly:InternalsVisibleTo(\"CSharpCarClient\")] namespace CarLibrary;<\/p>\n<p>The InternalsVisibleTo attribute takes the name of the project that can see into the class that has the attribute set. Note that other projects cannot \u201cask\u201d for this permission; it must be granted by the project holding the internal types.<\/p>\n<p>\u25a0 Note  previous versions of the .net framework allowed you to place assembly-level attributes into the AssemblyInfo.cs class, which still exists in .net but is autogenerated and not meant for developer consumption.<\/p>\n<p>Now you can update the CSharpCarClient project by adding the following code to the top-level statements:<\/p>\n<p>var internalClassInstance = new MyInternalClass();<\/p>\n<p>This works perfectly. Now try to do the same thing in the VisualBasicCarClient Main method.<\/p>\n<p>'Will not compile<br \/>\n'Dim internalClassInstance = New MyInternalClass()<\/p>\n<p>Because the VisualBasicCarClient library was not granted permission to see the internals, the previous line of code will not compile.<\/p>\n<p>Using the Project File<br \/>\nAnother way to accomplish the same thing is to use the updated capabilities in the .NET project file. Comment out the attribute you just added and open the project file for CarLibrary. Add the following ItemGroup in the project file:<\/p>\n<p><ItemGroup><br \/>\n<AssemblyAttribute Include=\"System.Runtime.CompilerServices.InternalsVisibleToAttribute\"><br \/>\n<_Parameter1>CSharpCarClient<\/_Parameter1><br \/>\n<\/AssemblyAttribute><br \/>\n<\/ItemGroup><\/p>\n<p>This accomplishes the same thing as using the attribute on a class and, in my opinion, is a better solution, since other developers will see it right in the project file instead of having to know where to look throughout the project.<\/p>\n<p>NuGet and .NET Core<br \/>\nNuGet is the package manager for the .NET Framework and .NET (Core). It is a mechanism to share software in a format that .NET applications understand and is the default mechanism for loading .NET and its related framework pieces (ASP.NET Core, EF Core, etc.). Many organizations package their standard assemblies for cross-cutting concerns (like logging and error reporting) into NuGet packages for consumption into their line-of-business applications.<\/p>\n<p>Packaging Assemblies with NuGet<br \/>\nTo see this in action, we will turn the CarLibrary into a NuGet package and then reference it from the two client applications.<br \/>\nThe NuGet Package properties can be accessed from the project\u2019s property pages. Right-click the CarLibrary project and select Properties. Navigate to the Package page and see the values that we entered before to customize the assembly. There are additional properties that can be set for the NuGet package (i.e., license agreement acceptance and project information such as URL and repository location).<\/p>\n<p>\u25a0 Note all of the values in the Visual studio package page ui can be entered into the project file manually, but you need to know the keywords. it helps to use Visual studio at least once to fill everything out, and then you can edit the project file by hand. You can also find all the allowable properties in the .net documentation.<\/p>\n<p>For this example, we don\u2019t need to set any additional properties except to select the \u201cGenerate NuGet package on build\u201d check box or update the project file with the following:<\/p>\n<p><PropertyGroup><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<ImplicitUsings>enable<\/ImplicitUsings><br \/>\n<Nullable>disable<\/Nullable><br \/>\n<Copyright>Copyright 2021<\/Copyright><br \/>\n<Authors>Phil Japikse<\/Authors><br \/>\n<Company>Apress<\/Company><\/p>\n<p><Product>Pro C# 10.0<\/Product><br \/>\n<PackageId>CarLibrary<\/PackageId><br \/>\n<Description>This is an awesome library for cars.<\/Description><br \/>\n<AssemblyVersion>1.0.0.1<\/AssemblyVersion><br \/>\n<FileVersion>1.0.0.2<\/FileVersion><br \/>\n<Version>1.0.0.3<\/Version><br \/>\n<GeneratePackageOnBuild>true<\/GeneratePackageOnBuild><br \/>\n<\/PropertyGroup><\/p>\n<p>This will cause the package to be rebuilt every time the software is built. By default, the package will be created in the bin\/Debug or bin\/Release folder, depending on which configuration is selected.<br \/>\nPackages can also be created from the command line, and the CLI provides more options than Visual Studio. For example, to build the package and place it in a directory called Publish, enter the following commands (in the CarLibrary project directory). The first command builds the assembly, and the second packages up the NuGet package.<\/p>\n<p>dotnet build -c Release<br \/>\ndotnet pack -o .\\Publish -c Release<\/p>\n<p>The CarLibrary.1.0.3.nupkg file is now in the Publish directory. To see its contents, open the file with any zip utility (such as 7-Zip), and you can see the entire content, which includes the assembly, but also additional metadata.<\/p>\n<p>Referencing NuGet Packages<br \/>\nYou might be wondering where the packages that were added in the previous examples came from. The location of NuGet packages is controlled by an XML-based file named NuGet.Config. On Windows, this file is in the %appdata%\\NuGet directory. This is the main file. Open it, and you will see several package sources.<\/p>\n<p><?xml version=\"1.0\" encoding=\"utf-8\"?><br \/>\n<configuration>\n<packageSources>\n<add key=\"nuget.org\" value=\"https:\/\/api.nuget.org\/v3\/index.json\" protocolVersion=\"3\" \/><br \/>\n<add key=\"Microsoft Visual Studio Offline Packages\" value=\"C:\\Program Files (x86)\\ Microsoft SDKs\\NuGetPackages\\\" \/>\n<\/packageSources>\n<\/configuration><\/p>\n<p>The previous file listing shows two sources. The first points to NuGet.org, which is the largest NuGet package repository in the world. The second is on your local drive and is used by Visual Studio as a cache of packages.<br \/>\nThe important item to note is that NuGet.Config files are additive by default. To add additional sources without changing the list for the entire system, you can add additional NuGet.Config files. Each file is valid for the directory that it\u2019s placed in as well as any subdirectory. Add a new file named NuGet.Config into the solution directory, and update the contents to this:<\/p>\n<p><?xml version=\"1.0\" encoding=\"utf-8\"?><br \/>\n<configuration>\n<packageSources>\n<add key=\"local-packages\" value=\".\\CarLibrary\\Publish\" \/>\n<\/packageSources>\n<\/configuration><\/p>\n<p>You can also reset the list of packages by adding <clear\/> into the <packageSources> node, like this:<\/p>\n<p><?xml version=\"1.0\" encoding=\"utf-8\"?><br \/>\n<configuration>\n<packageSources>\n<clear \/><br \/>\n<add key=\"local-packages\" value=\".\\CarLibrary\\Publish\" \/><br \/>\n<add key=\"NuGet\" value=\"https:\/\/api.nuget.org\/v3\/index.json\" \/>\n<\/packageSources>\n<\/configuration><\/p>\n<p>\u25a0 Note  if you are using Visual studio, you will have to restart the ide before the updated nuget.config<br \/>\nsettings take effect.<\/p>\n<p>Remove the project references from the CSharpCarClient and VisualBasicCarClient projects, and then add package references like this (from the solution directory):<\/p>\n<p>dotnet add CSharpCarClient package CarLibrary dotnet add VisualBasicCarClient package CarLibrary<\/p>\n<p>Once the references are set, build the solution and look at the output in the target directories (bin\\ Debug\\new6.0), and you will see the CarLibrary.dll in the directory and not the CarLibrary.nupkg file. This is because .NET unpacks the contents and adds in the assemblies contained as direct references.<br \/>\nNow set one of the clients as the startup project and run the application, and it works exactly like before. Next, update the version number of CarLibrary to 1.0.0.4, and repackage it. In the Publish directory,<br \/>\nthere are now two CarLibrary NuGet packages. If you rerun the add package commands, the project will be updated to use the new version. If the older version was preferred, the add package command allows for adding version numbers for a specific package.<\/p>\n<p>Publishing Console Applications (Updated .NET 5\/6)<br \/>\nNow that you have your C# CarClient application (and its related CarLibrary assembly), how do you get it out to your users? Packaging up your application and its related dependencies is referred to as publishing. Publishing .NET Framework applications required the framework to be installed on the target machine, and then it was a simple copy of the application executable and related files to run your application on another machine.<br \/>\nAs you might expect, .NET applications can also be published in a similar manner, which is referred to as a framework-dependent deployment. However, .NET applications can also be published as a self- contained application, which doesn\u2019t require .NET to be installed at all!<br \/>\nWhen publishing applications as self-contained, you must specify the target runtime identifier (RID) in the project file or through the command-line options. The runtime identifier is used to package your application for a specific operating system. When a runtime identifier is specified, the publish process defaults to self-contained. For a full list of available runtime identifiers (RIDs), see the .NET RID Catalog at https:\/\/docs.microsoft.com\/en-us\/dotnet\/core\/rid-catalog.<br \/>\nApplications can be published directly from Visual Studio or by using the .NET CLI. The command for the CLI is dotnet publish. To see all the options, use dotnet publish -h. Table 16-1 explores the common options used when publishing from the command line.<\/p>\n<p>Table 16-1. Some Options for Application Publishing<\/p>\n<p>Option  Meaning in Life<br \/>\n--use-current-runtime   Use current runtime as the target runtime.<br \/>\n-o, --output <OUTPUT_DIR>   The output directory to place the published artifacts in.<br \/>\n--self-contained    Publish the .NET Runtime with your application so the runtime doesn\u2019t need to be installed on the target machine. The default is true if a runtime identifier is specified.<br \/>\n--no-self-contained Publish your application as a framework-dependent application without the .NET Runtime. A supported .NET Runtime must be installed to run your application.<br \/>\n-r <RUNTIME_IDENTIFIER>--runtime<br \/>\n<RUNTIME_IDENTIFIER>    The target runtime to publish for. This is used when creating a self-contained deployment. The default is to publish a framework-dependent application.<br \/>\n-c debug | release--configuration debug | release   The configuration to publish for. The default for most projects is debug.<br \/>\n-v, --verbosity <d|detailed|diag|dia gnostic|m|minimal|n|normal|q|quiet>    Set the MSBuild verbosity level. Allowed values are q\/quiet, m\/minimal, n\/normal, d\/detailed, and diag\/diagnostic.<\/p>\n<p>Publishing Framework-Dependent Applications<br \/>\nWhen a runtime identifier isn\u2019t specified, framework-dependent deployment is the default for the dotnet publish command. To package your application and the required files, all you need to execute with the CLI is the following command:<\/p>\n<p>dotnet publish<\/p>\n<p>\u25a0 Note  the publish command uses the default configuration for your project, which is typically debug.<\/p>\n<p>This places your application and its supporting files (six files in total) into the bin\\Debug\\net6.0\\ publish directory. Examining the files that were added to that directory, you see two *.dll files (CarLibrary.dll and CSharpCarClient.dll) that contain all the application code. As a reminder,<br \/>\nthe CSharpCarClient.exe file is a packaged version of dotnet.exe that is configured to launch CSharpCarClient.dll. The CSharpCarClient.deps.json file lists all the dependencies for the application, and the CSharpCarClient.runtimeconfig.json file specifies the target framework (net6.0) and the framework version. The last file is the debug file for the CSharpCarClient.<br \/>\nTo create a release version (which will be placed in the bin\\release\\net6.0\\publish directory), enter the following command:<\/p>\n<p>dotnet publish -c release<\/p>\n<p>Publishing Self-Contained Applications<br \/>\nLike framework-dependent deploys, self-contained deployments include all your application code and referenced assemblies, but also include the .NET Runtime files required by your application. To publish<\/p>\n<p>your application as a self-contained deployment, you must include a runtime identifier in the command (or in your project file). Enter the following CLI command that places the output into a folder named selfcontained. If you are on a Mac or Linux machine, select osx-x64 or linux-x64 instead of win-x64.<\/p>\n<p>dotnet publish -r win-x64 -c release -o selfcontained<\/p>\n<p>\u25a0 Note new in .net 6, the --self-contained true option is no longer necessary. if a runtime identifier is specified, the publish command will use the self-contained process.<\/p>\n<p>This places your application and its supporting files (226 files in total) into the selfcontained directory. If you copied these files to another computer that matches the runtime identifier, you can run the application even if the .NET 6 Runtime isn\u2019t installed.<\/p>\n<p>Publishing Self-Contained Applications as a Single File<br \/>\nIn most situations, deploying 226 files (for an application that prints a few lines of text) is probably not the most effective way to get your application out to users. Fortunately, .NET 5 greatly improved the ability to publish your application and the cross-platform runtime files into a single file. This process is improved again with .NET 6, eliminating the need to have the native libraries exist outside of the single EXE, which was necessary in .NET 5 for Windows publishing.<br \/>\nThe following command creates a single-file, self-contained deployment package for 64-bit Windows operating systems and places the resulting files in a folder named singlefile.<\/p>\n<p>dotnet publish -r win-x64 -c release -o singlefile --self-contained true -p:PublishSingleFile=true<\/p>\n<p>When you examine files that were created, you will find a single executable (CSharpCarClient.exe) and a debug file (CSharpCarClient.pdb). While the previous publish process produced a large number of smaller files, the single file version of CSharpCarClient.exe clocks in at 60 MB! Creating the single file<br \/>\npublication packed all of the 226 files into the new single file. What was made up for in file count reduction was traded for in file size.<br \/>\nTo include the debug symbols file in the single file (to truly make it a single file), update your command to the following:<\/p>\n<p>dotnet publish -r win-x64 -c release -o singlefile -p:PublishSingleFile= true -p:DebugType=embedded<\/p>\n<p>Now you have one file that contains everything, but it\u2019s still quite large. One option that might help with file size is compression. The output can be compressed to save space, but this will most likely affect the startup time of your application even more. To enable compression, use the following command (all on one line):<\/p>\n<p>dotnet publish -c release -r win-x64 -o singlefilecompressed -p:PublishSingleFile= true -p:DebugType=embedded -p:EnableCompressionInSingleFile=true<\/p>\n<p>The .NET team has been working on file trimming during the publication process for the past few releases, and with .NET 6, it\u2019s out of preview and ready to be used. The file trimming process determines what can be removed from the runtime based on what your application uses. Part of the reason this process is now live is because the team has been annotating the runtime itself to remove the false warnings that<\/p>\n<p>were prevalent in .NET 5. New in .NET 6, the trimming process doesn\u2019t just look for assemblies that can be removed; it also looks for unused members. Use the following command to trim the single file output (all on one line):<\/p>\n<p>dotnet publish -c release -r win-x64 -o singlefilecompressedandtrimmed -p: PublishSingleFile=true -p:DebugType=embedded -p:EnableCompressionInSingleFile= true -p:PublishTrimmed=true<\/p>\n<p>The final step in this journey is to publish your app in a ready-to-run state. This can improve startup time since some of the JIT compilation is done ahead of time (AOT), during the publish process.<\/p>\n<p>dotnet publish -c release -r win-x64 -o singlefilefinal -p:PublishSingleFile=<br \/>\ntrue -p:DebugType=embedded -p:EnableCompressionInSingleFile=true -p:PublishTrimmed= true -p:PublishReadyToRun=true<\/p>\n<p>The final size of our application is 11 MB, far less than the 60 MB that we started with.<br \/>\nAs a final note, all of these settings can be configured in the project file for your application, like this:<\/p>\n<p><PropertyGroup><br \/>\n<OutputType>Exe<\/OutputType><br \/>\n<TargetFramework>net6.0<\/TargetFramework><br \/>\n<ImplicitUsings>enable<\/ImplicitUsings><br \/>\n<Nullable>disable<\/Nullable><br \/>\n<PublishSingleFile>true<\/PublishSingleFile><br \/>\n<SelfContained>true<\/SelfContained><br \/>\n<RuntimeIdentifier>win-x64<\/RuntimeIdentifier><br \/>\n<PublishTrimmed>true<\/PublishTrimmed><br \/>\n<DebugType>embedded<\/DebugType><br \/>\n<EnableCompressionInSinglefile>true<\/EnableCompressionInSinglefile><br \/>\n<PublishReadyToRun>true<\/PublishReadyToRun><br \/>\n<\/PropertyGroup><\/p>\n<p>With those values set in the project file, the command line becomes much shorter:<\/p>\n<p>dotnet publish -c release -o singlefilefinal2<\/p>\n<p>How .NET Locates Assemblies<br \/>\nSo far in this book, all the assemblies that you have built were directly related (except for the NuGet example you just completed). You added either a project reference or a direct reference between projects. In these cases (as well as the NuGet example), the dependent assembly was copied directly into the target directory of the client application. Locating the dependent assembly isn\u2019t an issue, since they reside on the disk right next to the application that needs them.<br \/>\nBut what about the .NET Framework? How are those located? Previous versions of .NET installed the framework files into the Global Assembly Cache (GAC), and all .NET Framework applications knew how to locate the framework files.<br \/>\nHowever, the GAC prevents the side-by-side capabilities in .NET Core, so there isn\u2019t a single repository of runtime and framework files. Instead, the files that make up the framework are installed together in C:\\ Program Files\\dotnet (on Windows), separated by version. Based on the version of the application (as specified in the .csproj file), the necessary runtime and framework files are loaded for an application from the specified version\u2019s directory.<\/p>\n<p>Specifically, when a version of the runtime is started, the runtime host provides a set of probing paths that it will use to find an application\u2019s dependencies. There are five probing properties (each of them optional), as listed in Table 16-2.<\/p>\n<p>Table 16-2. Application Probing Properties<\/p>\n<p>Option  Meaning in Life<br \/>\nTRUSTED_PLATFORM_ASSEMBLIES List of platform and application assembly file paths<br \/>\nPLATFORM_RESOURCE_ROOTS List of directory paths to search for satellite resource assemblies<br \/>\nNATIVE_DLL_SEARCH_DIRECTORIES   List of directory paths to search for unmanaged (native) libraries<br \/>\nAPP_PATHS   List of directory paths to search for managed assemblies<br \/>\nAPP_NI_PATHS    List of directory paths to search for native images of managed assemblies<\/p>\n<p>To see the default paths for these, create a new .NET Console application named FunWithProbingPaths.<br \/>\nUpdate the Program.cs file to the following top-level statements:<\/p>\n<p>Console.WriteLine(\"*** Fun with Probing Paths ***\"); Console.WriteLine($\"TRUSTED_PLATFORM_ASSEMBLIES: \");<br \/>\n\/\/Use ':' on non-Windows platforms<br \/>\nvar list = AppContext.GetData(\"TRUSTED_PLATFORM_ASSEMBLIES\")<br \/>\n.ToString().Split(';'); foreach (var dir in list)<br \/>\n{<br \/>\nConsole.WriteLine(dir);<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\nConsole.WriteLine($\"PLATFORM_RESOURCE_ROOTS: {AppContext.GetData (\"PLATFORM_RESOURCE_ROOTS\")}\"); Console.WriteLine();<br \/>\nConsole.WriteLine($\"NATIVE_DLL_SEARCH_DIRECTORIES: {AppContext.GetData (\"NATIVE_DLL_SEARCH_ DIRECTORIES\")}\");<br \/>\nConsole.WriteLine();<br \/>\nConsole.WriteLine($\"APP_PATHS: {AppContext.GetData(\"APP_PATHS\")}\"); Console.WriteLine();<br \/>\nConsole.WriteLine($\"APP_NI_PATHS: {AppContext.GetData(\"APP_NI_PATHS\")}\"); Console.WriteLine();<br \/>\nConsole.ReadLine();<\/p>\n<p>When you run this app, you will see most of the values come from the TRUSTED_PLATFORM_ASSEMBLIES variable. In addition to the assembly that is created for this project in the target directory, you will see a list of base class libraries from the current runtime directory, C:\\Program Files\\dotnet\\shared\\Microsoft. NETCore.App\\6.0.0 (your version number might be different).<br \/>\nEach of the files directly referenced by your application is added to the list as well as any runtime files that are required for your application. The list of runtime libraries is populated by one or more *.deps.json files that are loaded with the .NET Runtime. There are several in the installation directory for the SDK (used for building the software) and the runtime (used for running the software). With our simple example, the only file used is Microsoft.NETCore.App.deps.json.<br \/>\nAs your application grows in complexity, so will the list of files in TRUSTED_PLATFORM_ASSEMBLIES. For example, if you add a reference to the Microsoft.EntityFrameworkCore package, the list of required<\/p>\n<p>assemblies grows. To demonstrate this, enter the following command in Package Manager Console (in the same directory as the *.csproj file):<\/p>\n<p>dotnet add package Microsoft.EntityFrameworkCore<\/p>\n<p>Once you have added the package, rerun the application, and notice how many more files are listed. Even though you added only one new reference, the Microsoft.EntityFrameworkCore package has its dependencies, which get added into the trusted file list.<\/p>\n<p>Summary<br \/>\nThis chapter examined the role of .NET class libraries (aka .NET *.dlls). As you have seen, class libraries are<br \/>\n.NET binaries that contain logic intended to be reused across a variety of projects.<br \/>\nYou learned the details of partitioning types into .NET namespaces and the difference between a .NET and .NET Standard, got started with application configuration, and dove deep into the composition of class libraries. Next you learned how to publish .NET console applications. Finally, you learned how to package your applications using NuGet.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>CHAPTER 16 Building and Configuring Class Libraries For most of the examples so far in this book, you have created \u201cstand-alone\u201d executable applications, in which all the programming logic was packaged within a single assembly (.dll) and executed using dotnet.exe (or a copy of dotnet.exe named after the assembly). These assemblies were using little more [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[22],"class_list":["post-328","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-pro-csharp10-with-net6"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/328","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=328"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/328\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=328"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=328"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=328"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}