Search code examples
c#reflectionreflection.emit

Under what conditions can TypeBuilder.CreateType return null?


The TypeBuilder.CreateType() method is defined as nullable:

public Type? CreateType();

Under what conditions can it return null? The docs do not say.

I could dig into the source, but that would yield an untrustworthy answer (though interesting). Is there a documented explanation for this signature which I've missed?


Solution

  • Inter-site-crosspost-ahoy: https://github.com/dotnet/dotnet-api-docs/issues/7955


    Under what conditions can TypeBuilder.CreateType() return null?

    TL;DR: There is no situation when TypeBuilder.CreateType() will return null when CreateType() is being called by user-code. You can safely add a null-forgiving ! to a CreateType() call-site.


    The current source is at https://github.com/dotnet/runtime/blob/1a296c06fff8750b4658f6f7d901347e006744d0/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs#L1853

    • A quick browse of the current source shows that TypeBuilder.CreateType() only returns null when the TypeBuilder is the builder for an assembly's hidden <Module> class.
      • The <Module> class is a special type that allows assemblies/modules to have eager-initialized globals.
      • The C# language itself doesn't use <Module>, though other languages do (e.g. C++/CLI).
      • Since C# 9.0, you can now opt-in to adding <Module> members via the [ModuleInitializer] attribute.
      • Of course, if you're using Reflection.Emit then you will have always been able to use ModuleBuilder going back to .NET 1.x in 2001.
    • <Module>-building TypeBuilder instances cannot be directly instantiated by user-code as it requires a specific internal TypeBuilder() constructor invocation - instead this ctor is called only when you use the ModuleBuilder type.
    • The only situation when TypeBuilder.CreateType() method does indeed return null is inside void ModuleBuilder.CreateGlobalFunctions(), which basically ensures that a class <Module> will be defined in the generated dynamic-assembly.
    • So while the nullable annotation on Type? TypeBuilder.CreateType()'s return-type is technically correct (the best kind of correct), it's only there because the current API design is poor: it uses a single method for two separate use-cases when one of those cases is also internal-only.