Skip to main content

.NET Assembly

Introduction

In the world of .NET, an assembly is one of the most fundamental concepts you'll encounter. Simply put, an assembly is the building block of a .NET application - it's the compiled output of your code that the .NET runtime can execute. Whether you're building a simple console application or a complex web service, understanding assemblies is crucial to becoming proficient in .NET development.

An assembly is essentially a deployment unit that contains:

  • Compiled code (in the form of Intermediate Language or IL)
  • Metadata that describes the code
  • Resources required by the application
  • A manifest that provides assembly identity and dependency information

This article will guide you through the essentials of .NET assemblies, how they work, and how you can leverage them effectively in your projects.

What is a .NET Assembly?

An assembly is a compiled code library used for deployment, versioning, and security in .NET applications. When you compile a C# project, the compiler generates an assembly in the form of an executable file (.exe) or a dynamic link library (.dll).

Key Characteristics of Assemblies

  1. Self-describing: Every assembly contains metadata about itself, eliminating the need for external information like type libraries or IDL files.

  2. Version control: Assemblies have built-in version information that the .NET runtime uses for version management.

  3. Security boundary: The .NET security system uses assemblies as a unit for enforcing security permissions.

  4. Type boundary: Types in an assembly are only visible outside the assembly if explicitly marked as public.

  5. Deployment unit: Assemblies are the smallest deployable units in a .NET application.

Assembly Structure

A .NET assembly consists of four main parts:

1. Assembly Manifest

The manifest is like the assembly's ID card. It contains:

  • Assembly name and version
  • Culture information (for localization)
  • Strong name information (if the assembly is signed)
  • List of files that make up the assembly
  • Type references that are exported
  • Dependencies on other assemblies

2. Type Metadata

This contains descriptions of all the types defined in the assembly, including:

  • Class names, methods, and properties
  • Access modifiers
  • Implemented interfaces
  • Custom attributes

3. Microsoft Intermediate Language (MSIL) Code

This is the compiled code in an intermediate language that will be just-in-time (JIT) compiled to machine code when executed.

4. Resources

These can include images, strings, or any other data your application needs.

Types of Assemblies

In .NET, assemblies come in two main varieties:

Process Assemblies (EXE)

These are standalone executable applications that can be directly run by the operating system.

csharp
// Example of code that would compile into a process assembly (.exe)
using System;

class Program
{
static void Main(string[] args)
{
Console.WriteLine("This is an executable assembly!");
Console.ReadKey();
}
}

Library Assemblies (DLL)

These contain code that can be called from other assemblies but cannot be directly executed.

csharp
// Example of code that would compile into a library assembly (.dll)
namespace MyLibrary
{
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
}

Single-File vs. Multi-File Assemblies

Most assemblies are single-file assemblies, meaning all components are contained within a single physical file (.dll or .exe). However, .NET also supports multi-file assemblies where the components are split across multiple files, with one file containing the assembly manifest.

Working with Assemblies in C#

Creating an Assembly

When you compile a C# project, you're creating an assembly. Here's a basic example:

csharp
// File: MathLibrary.cs
namespace MathLibrary
{
public class Operations
{
public static int Add(int a, int b)
{
return a + b;
}

public static int Multiply(int a, int b)
{
return a * b;
}
}
}

To compile this into a library assembly using the command line:

bash
csc /target:library MathLibrary.cs

This will produce MathLibrary.dll.

Referencing an Assembly

To use an assembly in your code, you need to reference it. In Visual Studio, you can right-click on "References" or "Dependencies" in your project and select "Add Reference."

From code, you can then use the types defined in that assembly:

csharp
using System;
using MathLibrary;

class Program
{
static void Main(string[] args)
{
int result = Operations.Add(5, 3);
Console.WriteLine($"5 + 3 = {result}");

result = Operations.Multiply(5, 3);
Console.WriteLine($"5 × 3 = {result}");
}
}

// Output:
// 5 + 3 = 8
// 5 × 3 = 15

Examining Assembly Information at Runtime

.NET provides the System.Reflection namespace to examine assembly information programmatically:

csharp
using System;
using System.Reflection;

class Program
{
static void Main(string[] args)
{
// Get the currently executing assembly
Assembly assembly = Assembly.GetExecutingAssembly();

// Display assembly information
Console.WriteLine($"Assembly Name: {assembly.GetName().Name}");
Console.WriteLine($"Assembly Version: {assembly.GetName().Version}");
Console.WriteLine($"Assembly Location: {assembly.Location}");

// Get types in the assembly
Type[] types = assembly.GetTypes();
Console.WriteLine("\nTypes in the assembly:");
foreach (Type type in types)
{
Console.WriteLine($"- {type.FullName}");
}
}
}

// Example output:
// Assembly Name: MyApplication
// Assembly Version: 1.0.0.0
// Assembly Location: C:\Projects\MyApplication\bin\Debug\net6.0\MyApplication.dll
//
// Types in the assembly:
// - Program
// - Calculator
// - Helper

Global Assembly Cache (GAC)

The Global Assembly Cache (GAC) is a machine-wide cache for .NET Framework assemblies that are shared among multiple applications on the computer. Starting with .NET Core and continuing with .NET 5+ versions, the concept of the GAC has been deprecated in favor of side-by-side deployment of different versions.

In the .NET Framework world, to install an assembly to the GAC, you typically use the Global Assembly Cache Tool (gacutil.exe):

bash
gacutil /i MyAssembly.dll

Strong-Named Assemblies

A strong name uniquely identifies an assembly and prevents name collisions. It consists of:

  • The assembly's text name
  • Version number
  • Culture information
  • A public key and digital signature

To create a strong-named assembly, first generate a key pair:

bash
sn -k MyKeyPair.snk

Then, use the key in your project:

csharp
// AssemblyInfo.cs
using System.Reflection;
[assembly: AssemblyKeyFile("MyKeyPair.snk")]

Or in modern .NET projects, add it to the project file:

xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>MyKeyPair.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
</Project>

Assembly Versioning

.NET assemblies include version information that follows the format: Major.Minor.Build.Revision.

csharp
// AssemblyInfo.cs in .NET Framework
using System.Reflection;
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

In newer .NET projects, version information is typically specified in the project file:

xml
<PropertyGroup>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Version>1.0.0</Version>
</PropertyGroup>

Practical Example: Creating and Using a Shared Library

Let's put this knowledge into practice by creating a shared library and using it in an application.

Step 1: Create a Class Library Project

Create a new Class Library project named "UtilityLibrary":

csharp
// StringUtilities.cs
namespace UtilityLibrary
{
public static class StringUtilities
{
public static string Reverse(string input)
{
char[] chars = input.ToCharArray();
Array.Reverse(chars);
return new string(chars);
}

public static bool IsPalindrome(string input)
{
string normalized = input.ToLower().Replace(" ", "");
return normalized == Reverse(normalized);
}
}
}

Step 2: Build the Library

Build the project to create the UtilityLibrary.dll assembly.

Step 3: Create a Console Application to Use the Library

Create a new Console Application project and add a reference to the UtilityLibrary.

csharp
using System;
using UtilityLibrary;

class Program
{
static void Main(string[] args)
{
Console.WriteLine("String Utility Demonstration");
Console.WriteLine("---------------------------");

// Test the Reverse method
string original = "Hello, World!";
string reversed = StringUtilities.Reverse(original);
Console.WriteLine($"Original: {original}");
Console.WriteLine($"Reversed: {reversed}");

// Test the IsPalindrome method
string[] testStrings = {
"radar",
"hello",
"A man a plan a canal Panama",
"No lemon, no melon"
};

Console.WriteLine("\nPalindrome Check:");
foreach (var str in testStrings)
{
bool isPalindrome = StringUtilities.IsPalindrome(str);
Console.WriteLine($"'{str}' is {(isPalindrome ? "" : "not ")}a palindrome");
}

Console.ReadKey();
}
}

// Output:
// String Utility Demonstration
// ---------------------------
// Original: Hello, World!
// Reversed: !dlroW ,olleH
//
// Palindrome Check:
// 'radar' is a palindrome
// 'hello' is not a palindrome
// 'A man a plan a canal Panama' is a palindrome
// 'No lemon, no melon' is a palindrome

This example demonstrates the basic workflow of creating a library assembly and consuming it from another application.

Summary

.NET assemblies are the fundamental building blocks of .NET applications. They encapsulate compiled code, metadata, and resources into a deployment unit that the .NET runtime can load and execute. Understanding assemblies is critical for effectively building, deploying, and maintaining .NET applications.

Key points to remember:

  • Assemblies are self-describing units of deployment in .NET
  • They can be EXE (executable) or DLL (library) files
  • Each assembly contains compiled IL code, metadata, resources, and a manifest
  • Assemblies support strong naming for unique identification
  • Version information helps manage assembly dependencies
  • The reflection API allows for runtime inspection of assemblies

As you continue your .NET journey, developing a deeper understanding of assemblies will enable you to build more modular, maintainable, and robust applications.

Additional Resources

Exercises

  1. Create a class library that provides at least three different math operations. Reference it from a console application and demonstrate each operation.

  2. Use reflection to list all the methods available in the System.String class at runtime.

  3. Create a strong-named assembly and explore its properties using the Strong Name Tool (sn.exe) or through reflection.

  4. Build a simple plugin system where your main application loads functionality from external assemblies at runtime using Assembly.Load().

  5. Create two versions of the same assembly and write code that can use both versions side-by-side in the same application (hint: look into assembly loading contexts).



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)