Skip to content

[API Proposal] Add persist able AssemblyBuilder implementation #83988

Closed
@buyaa-n

Description

@buyaa-n

Contributes to Support equivalent of AssemblyBuilder.Save to save in-memory IL to an assembly

As per #62956 we plan to a add managed implementation of AssemblyBuilder that could save in-memory IL to an assembly file/stream.

Proposed design 1

Initial version would only support Save, no Run, therefore the factory method not accepting AssemblyBuilderAccess that defines the access mode for the assembly. It is the current prototype design.

namespace System.Reflection.Emit
{
+    public sealed class AssemblyFileBuilder : AssemblyBuilder
+    {
+        internal AssemblyFileBuilder(AssemblyName name, IEnumerable<CustomAttributeBuilder>? assemblyAttributes) { }

         // New static methods without AssemblyBuilderAccess, because initial version will only support Save
+        public static AssemblyFileBuilder DefineDynamicAssembly(AssemblyName name) { throw null; }
+        public static AssemblyFileBuilder DefineDynamicAssembly(AssemblyName name, Enumerable<CustomAttributeBuilder>? assemblyAttributes) { throw null; }
	
        // New instance methods
+        public void Save(Stream stream) { }
+        public void Save(string assemblyFileName) { } // same as in .NET Framework
+    }
}

Usage example

using System.Reflection.Emit;

public class MyType
{
    public void MyMethod(AssemblyName assemblyName, string fileName)
    {
	AssemblyFileBuilder builder = AssemblyFileBuilder.DefineDynamicAssembly(assemblyName);
	// ... Add module and other members 
	builder.Save(fileName);
    }
}

Alternative design 2:

The new persisted AssemblyBuilder type implementation will not exposed, instead we add Save into the abstract class AssemblyBuilder and protected abstract SaveCore method for implementation. Use the same factory method for creating AssemblyBuilder. This need for using reflection in order to create persisted AssemblyBuilder.

namespace System.Reflection.Emit
{
    public abstract class AssemblyBuilder : Assembly
    {
+        public void Save(Stream stream) { }
+        public void Save(string assemblyFileName) { }
+        protected abstract void SaveCore(Stream stream);
    }

    [Flags]
    public enum AssemblyBuilderAccess
    {
        Run = 1,
+	Save = 2,
        RunAndCollect = 8 | Run,
    }
}

Usage example

using System.Reflection.Emit;

public class MyType
{
    public void MyMethod(AssemblyName assemblyName, string fileName)
    {
	AssemblyBuilder builder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save);
	// ... Add module and other members 
	builder.Save(fileName);
    }
}

Alternative design 3:

Comment by @jkotas for above Alternative design 2:

  • Supporting both the runtime and persisted assemblies through the same API creates a choke-point for trimming and AOT. Save mode that somebody may want to use to build a compiler is compatible with trimming and AOT. The dynamic time mode is not compatible with AOT, and brings in additional runtime support. We should have two distinct APIs for the dynamic and persistent modes.
  • The core assembly should be passed in as Assembly to avoid ambiguity about what kind of name it is.

With these two points incorporated, the design would be:

 class AssemblyBuilder
 {
     // Existing APIs
     [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")]
     public static AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access);
     [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")]
     public static AssemblyBuilder DefineDynamicAssembly(AssemblyName name, AssemblyBuilderAccess access, System.Collections.Generic.IEnumerable<CustomAttributeBuilder>? assemblyAttributes);

+    // New API - note that it does not have RequiresDynamicCode annotation
+    public static AssemblyBuilder DefinePersistedAssembly(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null);

+    public void Save(Stream stream) { }
+    public void Save(string assemblyFileName) { } // same as in .NET Framework
+    protected abstract void SaveCore(Stream stream);
 }

AssemblyBuilderAccess is not used in this design because it will only support "Save" initially and eventually we allow Run without requiring to specify Save/Run explicitly.

Usage example

using System.Reflection.Emit;

public class MyType
{
    public void MyMethod(AssemblyName assemblyName, string fileName)
    {
	AssemblyBuilder builder = AssemblyBuilder.DefinePersistedAssembly(assemblyName, AssemblyBuilderAccess.Save);
	// ... Add module and other members 
	builder.Save(fileName);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions