Skip to content

[API Proposal]: ExtensionMarkerAttribute #118179

@jcouv

Description

@jcouv

Background and motivation

As part of the new extension everything feature, Roslyn will emit a new [ExtensionMarkerNameAttribute(...)] attribute in some cases, all documented in the linked spec. This issue tracks adding the attribute to the runtime as well, so it can be shared.

Relates to feature test plan dotnet/roslyn#76130

API Proposal

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]

public sealed class ExtensionMarkerNameAttribute : Attribute
{
    public ExtensionMarkerNameAttribute(string name)
        => Name = name;

    public string Name { get; }
}

Note: in .NET 10, we're only planning to use this attribute on methods and properties. The other targets may be used in later releases.

API Usage

The attribute will be disallowed to be used in source by newer C# compiler (like we do with other compiler-specific attributes, like [Dynamic], [IsReadOnly], [IsUnmanaged], ...).

Here's an example of how the attribute will be used in metadata:

public static class E
{
    extension(int)
    {
        public static void M() { }
    }
}
.class public auto ansi abstract sealed beforefieldinit E
    extends System.Object
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )

    // grouping type (carries the IL-level view of type parameters of extension declaration, which allows for stable public APIs)
    .class nested public auto ansi sealed specialname '<Extension>$8048A6C8BE30A622530249B904B537EB'<$T0>
        extends System.Object
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )

        // marker type (carries the type parameters of extension declaration)
        .class nested public auto ansi abstract sealed specialname '<Marker>$2789E59A55056F0AD9E820EBD5BCDFBF'<T>
            extends System.Object
        {
            // marker method (carries extension parameter)
            .method public hidebysig specialname static void '<Extension>$' ( !T '' ) cil managed
            {
                .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
                IL_0000: ret
            }
        }

        // extension method (stub)
        .method public hidebysig static void M () cil managed
        {
            // attributes encodes '<Marker>$2789E59A55056F0AD9E820EBD5BCDFBF'
            .custom instance void System.Runtime.CompilerServices.ExtensionMarkerNameAttribute::.ctor(string) = (
                01 00 29 3c 4d 61 72 6b 65 72 3e 24 32 37 38 39
                45 35 39 41 35 35 30 35 36 46 30 41 44 39 45 38
                32 30 45 42 44 35 42 43 44 46 42 46 00 00
            )
            IL_0000: ldnull
            IL_0001: throw
        }
    }

    // implementation method
    .method public hidebysig static void M<T> () cil managed
    {
        IL_0000: nop
        IL_0001: ret
    }
}

Alternative Designs

Name property or field

One thing I'd like to confirm is whether the Name property should be a property or a field. It looks like all the attributes embedded by Roslyn have a field, not a property.

AttributeTargets

Options:

  1. as proposed (targets we think we may need eventually)
  2. use the minimal set needed for C# 14 (only .Method and .Property)
  3. use broader set, either adding .Constructor (which we don't think we'll need) or using AttributeTargets.All (includes other clearly irrelevant targets like .Assembly, .Module, .Parameter, .ReturnType, ...)

Note on forward compatibility:
The C# compiler in .NET 10 ignores any usage of the attribute in places that are unexpected/disallowed. Such usages could come from IL or from another compiler (which doesn't disallow using the attribute in source).
The compiler also ignores any member kinds that it doesn't understand/expect inside the extension metadata. For example, it ignores events, nested types, fields, etc.

Risks

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions