In the .NET framework, an assembly functions as a self-describing deployment unit executed within an application domain (AppDomain). Before an application can execute, the runtime must load its corresponding assemblies into an AppDomain. The distinction between private and shared assemblies arises from how these units are managed and accessed across different application domains.
Private Assemblies
Private assemblies represent the standard deployment model for most .NET applications. When a local project is compiled, it generates a DLL or EXE file acting as a private assembly. If multiple application domains utilize the same private assembly, the runtime creates a separate copy for each domain. This means multiple instances of the same code exist within a single process, leading to higher memory consumption.
Shared Assemblies
Shared assemblies allow multiple application domains to access a single copy of the code, significantly reducing memory overhead. This domain-neutral code sharing is a key advantage in scenarios involving numerous dependent applications. These assemblies are typically stored in the Global Assembly Cache (GAC) rather than an application's local directory. Deployment requires Windows Installer (MSI) rather than simple file copying, as the GAC requires specific registration.
Establishing Unique Identity with Strong Names
Shared assemblies require a globally unique name, known as a strong name. This name prevents naming conflicts and ensures that an assembly cannot be substituted by a malicious version. A strong name consists of the assembly's text name, version number, culture information, and a public/private key pair. The cryptographic key pair guarantees uniqueness and allows the runtime to verify the assembly's origin.
Generating a Key Pair
The Strong Name tool (Sn.exe), included with the .NET SDK, manages keys and signing. To create a new key pair stored in a file, use the following command:
sn -k CompanyKey.snkThis generates a file containing both the public and private keys necessary for signing.
Applying the Strong Name to an Assembly
To sign an assembly, reference the key file within the project's AssemblyInfo file. Visual Studio automatically creates this file for new projects. Apply the AssemblyKeyFileAttribute to link the assembly with the key pair:
[assembly: AssemblyKeyFileAttribute(@"..\Resources\CompanyKey.snk")]Upon compilation, the assembly is signed with the private key. The public key is embedded in the manifest, allowing consumers to verify the signature.
Installing Assemblies into the Global Assembly Cache
Once signed, the assembly must be registered in the GAC to be shared. The Global Assembly Cache Utility (gacutil.exe) handles this registration. Use the -i flag to install an assembly:
gacutil -i SharedMathLibrary.dllThis command copies the assembly into the system-wide cache, making it available to any application on the machine. Unlike private assemblies, referencing a shared assembly from the GAC does not create a local file copy in the application's directory.
Practical Example: Building a Shared Utility Library
Step 1: Create the Class Library
Start a new Class Library project named SharedMathLibrary. Define a utility class providing a public method:
using System;
namespace SharedMathLibrary
{
public class CalculatorService
{
public string FetchCurrentTimestamp()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
}
}Step 2: Sign the Assembly
Open a command prompt and generate a strong name key:
sn -k signingKey.snkMove the key file to the project directory. Open AssemblyInfo.cs and add the attribute pointing to this key file:
[assembly: AssemblyKeyFile("signingKey.snk")]Build the project in Release mode to produce the signed DLL.
Step 3: Register in the GAC
Deploy the signed assembly to the GAC:
gacutil -i SharedMathLibrary.dllStep 4: Consume the Shared Assembly
Create a new Console Application named ClientApp. Add a reference to the shared assembly. Visual Studio will locate it within the GAC. Import the namespace and invoke the method:
using System;
using SharedMathLibrary;
namespace ClientApp
{
class Program
{
static void Main(string[] args)
{
var service = new CalculatorService();
string timestamp = service.FetchCurrentTimestamp();
Console.WriteLine($"Timestamp from shared library: {timestamp}");
}
}
}Executing the client application demonstrates successful communication with the shared assembly. The library resides in the GAC, and no local copy exists in the client's output directory, illustrating the efficient memory usage characteristic of shared assemblies.