Skip to content

Latest commit

 

History

History
109 lines (82 loc) · 3.32 KB

File metadata and controls

109 lines (82 loc) · 3.32 KB

Meziantou.Framework.PublicApiGenerator

Meziantou.Framework.PublicApiGenerator generates compilable C# files that represent the public API of a .NET assembly.

It can read metadata directly from a .dll or .exe (via PEReader and MetadataReader) or from a loaded Assembly. Both paths produce the same model and deterministic output.

Usage

using Meziantou.Framework.PublicApiGenerator;

var files = PublicApi.Generate(
    assemblyPath: "/path/to/MyLibrary.dll",
    options: new PublicApiOptions
    {
        FileLayout = PublicApiFileLayout.OneFilePerNamespace,
        IncludeAutoGeneratedComment = false,
    });

foreach (var file in files)
{
    Console.WriteLine(file.RelativePath);
    Console.WriteLine(file.Content);
}

Multi-target usage

You can merge multiple target frameworks into one output and emit conditional compilation blocks for target-specific differences:

using Meziantou.Framework.PublicApiGenerator;

var files = PublicApi.Generate(
    assemblySources:
    [
        "/path/to/MyLibrary.netstandard2.0.dll",
        "/path/to/MyLibrary.net10.0.dll",
    ]);

If needed, you can provide explicit framework monikers for specific entries:

using Meziantou.Framework.PublicApiGenerator;

var files = PublicApi.Generate(
    assemblySources:
    [
        new AssemblySource("/path/to/MyLibrary.netstandard2.0.dll", ".NETStandard,Version=v2.0"),
        new AssemblySource("/path/to/MyLibrary.net10.0.dll", ".NETCoreApp,Version=v10.0"),
    ]);

If TargetFrameworkMoniker is omitted (or empty), the generator infers it from assembly metadata.

Conditional blocks are kept as narrow as possible, so that adding a target framework produces a small diff. A type stays unconditional when the target frameworks only differ by their attributes, their members or their base type list; only the parts that are not shared by every target framework are wrapped in a conditional block:

public sealed class CronExpression : IRecurrenceRule
#if NET10_0
    , System.IParsable<CronExpression>
#endif
{
    public static CronExpression Parse(string expression) => throw null;
#if NET10_0
    public static CronExpression Parse(System.ReadOnlySpan<char> expression) => throw null;
#endif
}

The whole type is wrapped in a conditional block only when it cannot be merged, for instance when it does not exist in every target framework, or when its modifiers or generic constraints differ.

Memory safety rules

For assemblies compiled with the updated memory safety rules (<Features>$(Features);updated-memory-safety-rules</Features>), the unsafe modifier reflects the members the compiler marked as requiring an unsafe context, whatever their signature. Pointers in a signature no longer imply unsafe, and delegate declarations are never unsafe. Generated files may therefore need the same feature to be compiled.

For other assemblies, a member is unsafe when a pointer type appears in its signature, which matches the compatibility mode of the compiler.

Example output

Input API:

namespace Demo;

public sealed class Sample
{
    public string? Echo(string? value) => value;
}

Generated API:

// Target Frameworks: net10.0
#nullable enable

namespace Demo
{
    public sealed class Sample
    {
        public string? Echo(string? value) => throw null;
    }
}