Description
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);
}
}