C# 9.0 bringt prägnante, unveränderbare Typen

Seite 4: Modul-Initialisierer

Inhaltsverzeichnis

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 Methode Util.GetValue() aufruft.

Ausgabe von Listing 4 bis 6 (Abb. 1)

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).

Funktion eines Source Code Generator (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.