C# 9.0 bringt prägnante, unveränderbare Typen
Seite 4: Modul-Initialisierer
Ein Modul-Initialisierer (engl. Module Initializer) ist eine Methode, die beim Laden eines .NET-Moduls (entspricht einer .NET-Assembly) von der .NET-Laufzeitumgebung automatisch aufgerufen wird. Der Aufruf erfolgt vor allen anderen Codeausführungen. Das bedeutet, dass bei einem Startmodul ein Modul-Initialisierer vor Main()
ausgeführt wird. Bei einem DLL-Modul wird der Modul-Initialisierer vor der ersten Methode ausgeführt, die in der DLL aufgerufen wird .
Ein Modul-Initialisierer ist eine Methode, die folgende Voraussetzungen erfüllen muss:
- kompiliert mit C# 9.0 oder höher
- Runtime .NET 5.0 oder höher
- Methode ist in Klasse, die public oder internal ist
- Methode ist public oder internal
- Methode ist statisch (static)
- Methode ist parameterlos
- Methode ist hat keinen Rückgabewert (void)
- Methode ist nicht generisch
- Methode ist annotiert mit
[System.Runtime.CompilerServices.ModuleInitializerAttribute]
Es darf mehr als einen Modul-Initialisierer in einer DLL geben. Alle Modul-Initialisierer werden in der Reihenfolge aufgerufen, wie die Runtime sie im Kompilat findet.
Listing 5 bis 7 zeigen ein Beispiel mit einem Hauptmodul und einem referenzierten Modul.
class Program
{
static void Main(string[] args)
{
CUI.H1("C# 9.0 Demos");
Console.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription + " on " + System.Runtime.InteropServices.RuntimeInformation.OSDescription);
Console.WriteLine("Ergebnis: " + new Hilfsklassen.Util().GetValue());
CUI.H1("Fertig!");
}
}
Listing 5: Hauptprogamm im Hauptmodul
[ModuleInitializer]
public static void ModuleInitializer()
{
var ass = System.Reflection.Assembly.GetExecutingAssembly();
CUI.Print("Modul wird geladen: " + ass.GetName().Name + " v" + ass.GetName().Version.ToString() + " von " + FileVersionInfo.GetVersionInfo(ass.Location).CompanyName, ConsoleColor.Cyan);
}
Listing 6: Modul-Initialisierer im Hauptmodul
using ITVisions;
using System;
using System.Runtime.CompilerServices;
namespace Hilfsklassen
{
public class Util
{
public int GetValue()
{
return 42;
}
}
public class ModuleInitializerClass2
{
[ModuleInitializer]
public static void ModuleInitializer()
{
var ass = System.Reflection.Assembly.GetExecutingAssembly().GetName();
CUI.Print("ModuleInitializerClass2: Modul wird geladen: " + ass.Name + " v" + ass.Version.ToString(), ConsoleColor.Cyan);
}
}
public class ModuleInitializerClass
{
[ModuleInitializer]
public static void ModuleInitializer()
{
var ass = System.Reflection.Assembly.GetExecutingAssembly().GetName();
CUI.Print("ModuleInitializerClass1: Modul wird geladen: " + ass.Name + " v" + ass.Version.ToString(), ConsoleColor.Cyan);
}
}
}
Listing 7: Klassen im Modul Hilfsklassen.dll
inklusive zwei Modul-Initialisierern
In der Ausgabe des Beispiels in Abbildung 1 sieht man:
- Der Modul-Initialisierer im Hauptmodul wird vor
Main()
aufgerufen. - Die beiden Modul-Initialisierer im Modul
Hilfsklassen.dll
werden erst aufgerufen, wenn das Hauptprogramm erst mal auf etwas in der DLL zugreift, also die MethodeUtil.GetValue()
aufruft.
Source Code Generators
Eine weitere größere Neuerung in C# 9.0 sind Source Code Generators, mit denen Entwickler zusätzlichen Programmcode zur Kompilierungszeit erzeugen können, der zusammen mit dem eigentlichen Programmcode kompiliert wird (s. Abb. 2).
(Bild: Microsoft)
Damit kann man zum Beispiel Annotationen eine Bedeutung geben im Sinne aspektorientierter Programmierung (AOP). Für Microsoft sollen die neuen Generatoren den Weg zu einem allgemeinen Ahead-of-Time-Compiler ebnen, der in .NET 5.0 noch fehlt.
Ein Source Code Generator ist eine .NET-Klasse, die die Schnittstelle Microsoft.CodeAnalysis.ISourceGenerator
mit diesen beiden Methoden realisiert:
- void Initialize(GeneratorInitializationContext context)
- void Execute(GeneratorExecutionContext context)
Listing 8 zeigt einen Generator, der eine Klasse HelloWorld
erzeugt.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace SourceGeneratorSamples
{
[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var source = @"
using System;
namespace HelloWorldGenerated
{
public static class HelloWorld
{
public static void SayHello()
{
Console.WriteLine(""Hallo aus der Assembly "" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
}
}
}";
// Code wird injiziert
context.AddSource("helloWorldGenerator", SourceText.From(source, Encoding.UTF8));
}
/// <param name="context"></param>
public void Initialize(GeneratorInitializationContext context)
{
}
}
}
Listing 8: Ein ganz einfacher Source Code Generator
Diese Generator-Klasse muss in eine DLL-Assembly kompiliert werden und benötigt Verweise auf die NuGet-Pakete Microsoft.CodeAnalysis.Csharp
und Microsoft.CodeAnalysis.Analyzers
. Diese DLL können Entwickler dann in einem anderen Projekt wie einen Rosyln-Analyzer einbinden. Notwendig bei der <ProjectReference>
sind die die Zusatzattribute OutputItemType="Analyzer" ReferenceOutputAssembly="false"
.
<Project Sdk="Microsoft.NET.Sdk">
…
<ItemGroup>
<ProjectReference Include="..\CSharpSourceCodeGenerators\CSharpSourceCodeGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
…
</Project>
Danach steht innerhalb dieses Projekts die Klasse HelloWorld
zur Verfügung:
HelloWorldGenerated.HelloWorld.SayHello()
Da das ein sehr umfangreiches Thema ist, sei hier auf die Einträge "Introducing C# Source Generators" und "New C# Source Generator Samples" im .NET-Blog sowie die Dokumentation auf GitHub verwiesen.