Instead of placing all code into one monolithic binary file, C# and the underlying CLI framework allow you to spread code across multiple assemblies. This approach enables you to reuse assemblies across multiple executables.
The HelloWorld program is one of the most trivial programs you can write. Real-world programs are more complex, and as complexity increases, it helps to organize the complexity by breaking programs into multiple parts. To do this, developers move portions of a program into separate compiled units called class libraries or, simply, libraries. Programs then reference and rely on class libraries to provide parts of their functionality. The power of this concept is that two programs can rely on the same class library, thereby sharing the functionality of that class library across both programs and reducing the total amount of code needed.
With this approach, it is possible to write features once, place them into a class library, and allow multiple programs to include those features by referencing the same class library. Later in the development cycle, when developers fix a bug or add functionality to the class library, all the programs will have access to the increased functionality, just because they continue to reference the now improved class library.
Frequently, the code we write could be useful to more than one program. Imagine, for example, using the Longitude, Latitude, and Coordinate classes from a mapping program and a digital photo geocoding program or writing a command-line parser class. Classes and sets of classes like these can be written once and then reused from many different programs. As such, they need to be grouped together into an assembly called a library or class library and written for the purposes of reuse rather than only within a single program.
To create a library rather than a console project, follow the same directions as provided in Chapter 1, with one exception: For Dotnet CLI, use Class Library or classlib for the template.
Similarly, with Visual Studio 2019, from the File->New Project… menu item (Ctrl+Shift+N), use the Search text box to find all Class Library templates, and then select Class Library (.NET Standard)—the Visual C# version, of course. Use GeoCoordinates for the project name.
Next, place the source code from Listing 10.4 into separate files for each struct and name the file after the struct name and build the project. Building the project will compile the C# code into an assembly—a GeoCoordinates.dll file—and place it into a subdirectory of .\bin\.
Given the library, we need to reference it from a program. For example, for a new console program using the Program class from Listing 10.3, we need to add a reference to the GeoCoordinates.dll assembly, identifying where the library is located and embedding metadata that uniquely identifies the library into the program. There are several ways to do this. First, you can reference the library project file (*.csproj), thus identifying which project contains the library source code and forming a dependency between the two projects. You can’t compile the program referencing the library until the library is compiled. This dependency causes the library to compile (if it isn’t compiled already) when the program compiles.
The second approach is to reference the assembly file itself. In other words, reference the compiled library (*.dll) rather than the project. This makes sense when the library is compiled separately from the program, such as by another team within your organization.
Third, you can reference a NuGet package, as described in the next section.
Note that it isn’t only console programs that can reference libraries and packages. In fact, any assembly can reference any other assembly. Frequently, one library will reference another library, creating a chain of dependencies.
In Chapter 1, we discussed creating a console program. Doing so created a program that included a Main method—the entry point at which the program will begin executing. To add a reference to the newly created assembly, we continue where we left off with an additional command for adding a reference:
dotnet add .\HelloWorld\HelloWord.csproj package .\GeoCordinates\bin\Debug\netcoreapp2.0\GeoCoordinates.dll
Following the add argument is a file path for the compiled assembly referenced by the project.
Rather than referencing the assembly, you can reference the project file. As already mentioned, this chains the projects together so that building the program will trigger the class library to compile first if it hasn’t compiled already. The advantage is that as the program compiles, it will automatically locate the compiled class library assembly—whether it be in the debug or release directory, for example. The command for referencing a project file is as follows:
dotnet add .\HelloWorld\HelloWord.csproj reference .\GeoCoordinates \ GeoCoordinates.csproj
If you have the source code for a class library and that source code changes frequently, consider referencing the class library using the class library project file rather than the compiled assembly.
Upon completion of either the project or the compiled assembly reference, your project can compile with the Program class source code found in Listing 10.3.
In Chapter 1, we also discussed creating a console program with Visual Studio. This created a program that included a Main method. To add a reference to the GeoCoordinates assembly, click the Project->Add Reference… menu item. Next, from the Projects\Solution tab, select the GeoCoordinates project and OK to confirm the reference.
Similarly, to add an assembly reference, follow the same process as before, clicking the Project->Add Reference… menu item. However, this time click the Browse… button and navigate to and select the GeoCordinates.dll assembly.
As with Dotnet CLI, you can compile the program project with the Program class source code found in Listing 10.3.
Starting with Visual Studio 2010, Microsoft introduced a library packaging system called NuGet. This system is intended to provide a means to easily share libraries across projects and between companies. Frequently, a library assembly is more than just a single compiled file. It might have configuration files, additional resources, and metadata associated with it. Unfortunately, before NuGet, there was no manifest that identified all the dependencies. Furthermore, there was no standard provider or package library for where the referenced assemblies could be found.
NuGet addresses both issues. Not only does NuGet include a manifest that identifies the author(s), companies, dependencies, and more, it also comes with a default package provider at NuGet.org where packages can be uploaded, updated, indexed, and then downloaded by projects that are looking to leverage them. With NuGet, you can reference a NuGet package (*.nupkg) and have it automatically installed from one of your preconfigured NuGet provider URLs.
The NuGet package is accompanied by a manifest (a *.nuspec file) that contains all the additional metadata included in the package. Additionally, it provides all the additional resources you may want—localization files, config files, content files, and so on. In the end, the NuGet package is an archive of all the individual resources combined into a single ZIP file—albeit with the .nupkg extension. If you rename the file with a *.zip extension, you can open and examine the file using any common compression utility.
To add a NuGet package to your project using Dotnet CLI requires executing a single command:
>dotnet add .\HelloWorld\HelloWorld.csproj package Microsoft.Extensions.Logging.Console
This command checks each of the registered NuGet package providers for the specified package and downloads it. (You can also trigger the download explicitly using the command dotnet restore.)
To create a local NuGet package, use the dotnet pack command. This command generates a GeoCoordinates.1.0.0.nupkg file, which you can reference using the add ... package command.
The digits following the assembly name correspond to the package version number. To specify the version number explicitly, edit the project file (*.csproj) and add a <Version>...</Version> child element to the PropertyGroup element.
If you followed the instructions laid out in Chapter 1, you already have a HelloWorld project. Starting with that project, you can add a NuGet package using Visual Studio 2019 as follows:
Upon completion of these steps, it is possible to begin using the Microsoft.Extensions.Logging.Console library, along with any dependencies that it may have (which are automatically added in the process).
As with Dotnet CLI, you can use Visual Studio to build your own NuGet package using the Build->Pack <Project Name> menu item. Similarly, you can specify the package version number from the Package tab of the Project Properties.
Once the package or project is referenced, you can begin using it as though all the source code was included in the project. Listing 10.7 shows, for example, how to use the Microsoft.Extensions.Logging library, and Output 10.2 shows the sample output.
This library Microsoft.Extensions.Logging.Console NuGet package is used to log data to the console. In this case, we log both an information message and a warning and the messages appear in the console.
If you also referenced the Microsoft.Extensions.Logging.Debug library, you could add an .AddDebug() invocation after or before the AddConsole() invocation. The result would be that output similar to Output 10.2 would also appear in the debug output window of Visual Studio (select the Debug->Windows->Output menu) or Visual Studio Code (with the View->Debug Console menu).
The Microsoft.Extensions.Logging.Console NuGet package has three dependencies, including Microsoft.Extensions.Logging. Each of these is listed under the Dependencies\Packages node of the project in the Visual Studio Explorer window of Visual Studio. By adding a NuGet package, all dependencies are automatically added.