Skip to content

Enable Library-Level Preview Features #77869

@geeknoid

Description

@geeknoid

Edited by @terrajobst. Original request at the end. The spec for this feature is here.

Background and motivation

In .NET 6, we've added a way to ship preview features in otherwise stable releases. It works by tagging APIs (and language features) as preview features and requiring the user's project to explicitly opt-into using them via the <EnablePreviewFeatures> property.

The feature was primarily designed to accommodate platform-level preview features, that is for features that span runtime, library, and language, such as generic math. However, we also believed that the feature would work for libraries that want to offer preview functionality in otherwise stable NuGet packages.

This feature is about providing a model that works great for library authors. For details, see the design document.

API Proposal

namespace System.Diagnostics.CodeAnalysis;

[AttributeUsage(AttributeTargets.Assembly |
                AttributeTargets.Module |
                AttributeTargets.Class |
                AttributeTargets.Struct |
                AttributeTargets.Enum |
                AttributeTargets.Constructor |
                AttributeTargets.Method |
                AttributeTargets.Property |
                AttributeTargets.Field |
                AttributeTargets.Event |
                AttributeTargets.Interface |
                AttributeTargets.Delegate, Inherited = false)]
public sealed class ExperimentalAttribute : Attribute
{
    public ExperimentalAttribute(string diagnosticId);
    public string DiagnosticId { get; }
    public string? UrlFormat { get; set; }
}

API Usage

Shipping an experimental API in a stable package

Martin builds a library that integrates with ASP.NET Core and provides features to make it easier to build services. The library is distributed as a NuGet package. The package is already in a version well past 1.0 and is considered stable.

He ships the package often and doesn't want to maintain multiple flavors of the package, one marked as prerelease and one marked as stable. However, he wants to expose new APIs without making the promise that these APIs are stable. He designs the new APIs as if they would be stable, that is, he puts them in the namespace they belong and names the types and members without any naming convention that implies that they aren't stable yet. If customer feedback is positive, the API will become stable as-is, otherwise he'll make changes to address the feedback.

In order to convey to the consumers that these APIs aren't stable yet, he puts the Experimental attribute on them:

namespace MartinsLibrary.FancyLogging
{
    public static class FancyLoggerExtensions
    {
        [Experimental("ML123", UrlFormat="https://martinslibrary.net/diagnostics/{0}")]
        public static IServiceCollection AddFancyLogging(this IServiceCollections services)
        {
            // ...
        }
    }
}

Consuming an experimental API

David builds a new version of Jabber.NET and consumes Martin's library. He very much wants to play with the new fancy logging Martin added. When David calls AddFancyLogging he gets a compilation error:

error ML123: FancyLoggerExtensions.AddFancyLogging() is not a stable API. In order to consume this experimental API, suppress this error from the call site.

David is happy to accommodate future breaking changes around the fancy logging feature. He understands that the diagnostic ID is specific to the fancy logging feature of Martin's library, so he decides to add a suppression to the project file:

<Project>
    <PropertyGroup>
        <!-- Fancy logging is an experimental APIs -->
        <NoWarn>$(NoWarn);ML123</NoWarn>
    </PropertyGroup>
</Project>
Original Request ### Background and motivation

Library developers often want to experiment with APIs before committing to long-term support. This is true within an organization, as well as within the open-source community. It would be very useful for the .NET infrastructure to support experimental interfaces in a first class way.

The idea is to flag the use of experimental APIs in calling code, to make developers aware that they're calling into an API which is not yet settled and is subject to change outside of the normal support model for the component.

The attribute would be supplemented by a Roslyn analyzer that reports use of experimental APIs. You can also imagine special treatment in IntelliSense and in DocFx for experimental APIs.

API Proposal

namespace System.Diagnostics.CodeAnalysis;

/// <summary>
/// Indicates that an API element is experimental and subject to change without notice.
/// </summary>
[AttributeUsage(
    AttributeTargets.Class |
    AttributeTargets.Struct |
    AttributeTargets.Enum |
    AttributeTargets.Interface |
    AttributeTargets.Delegate |
    AttributeTargets.Method |
    AttributeTargets.Constructor |
    AttributeTargets.Property |
    AttributeTargets.Field |
    AttributeTargets.Event |
    AttributeTargets.Assembly)]
public sealed class ExperimentalAttribute : Attribute
{
    /// <summary>
    /// Gets a human readable explanation for marking API as experimental.
    /// </summary>
    public string? Message { get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="ExperimentalAttribute"/> class.
    /// </summary>
    public ExperimentalAttribute()
    {
        // Intentionally left empty.
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ExperimentalAttribute"/> class.
    /// </summary>
    /// <param name="message">Human readable explanation for marking experimental API.</param>
    public ExperimentalAttribute(string message)
    {
        Message = Throws.IfNull(message);
    }
}

API Usage

[Experimental("Trying this out, maybe committed in the 3.1 release")]
public void DoSomethingNew();

[assembly: Experimental("This whole assembly is subject to change without notice")]

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.RuntimeblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions