Skip to content

Commit 1aaa0ec

Browse files
arttonoyantoddbaert
authored andcommitted
feat: Add Dependency Injection and Hosting support for OpenFeature (#310)
This pull request introduces key features and improvements to the OpenFeature project, focusing on Dependency Injection and Hosting support: - **OpenFeature.DependencyInjection Project:** - Implemented `OpenFeatureBuilder`, including `OpenFeatureBuilderExtensions` for seamless integration. - Added `IFeatureLifecycleManager` interface and its implementation. - Introduced `AddProvider` extension method for easy provider configuration. - Created `OpenFeatureServiceCollectionExtensions` for service registration. - **OpenFeature.Hosting Project:** - Added `HostedFeatureLifecycleService` to manage the lifecycle of feature providers in hosted environments. - **Testing Enhancements:** - Created unit tests for critical methods, including `OpenFeatureBuilderExtensionsTests` and `OpenFeatureServiceCollectionExtensionsTests`. - Replicated and tested `NoOpFeatureProvider` implementation for better test coverage. These changes significantly improve OpenFeature's extensibility and lifecycle management for feature providers within Dependency Injection (DI) and hosted environments. Signed-off-by: Artyom Tonoyan <[email protected]>
1 parent ccf0250 commit 1aaa0ec

31 files changed

+1425
-14
lines changed

Directory.Packages.props

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
<Project>
2-
2+
33
<PropertyGroup>
44
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
55
</PropertyGroup>
6-
6+
77
<ItemGroup Label="src">
88
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
9-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
9+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
10+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
11+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
12+
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
1013
<PackageVersion Include="System.Collections.Immutable" Version="1.7.1" />
1114
<PackageVersion Include="System.Threading.Channels" Version="6.0.0" />
1215
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
1316
</ItemGroup>
14-
17+
1518
<ItemGroup Label="test">
1619
<PackageVersion Include="AutoFixture" Version="4.18.1" />
1720
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
@@ -26,10 +29,11 @@
2629
<PackageVersion Include="SpecFlow.xUnit" Version="3.9.74" />
2730
<PackageVersion Include="xunit" Version="2.9.2" />
2831
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
32+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
2933
</ItemGroup>
30-
34+
3135
<ItemGroup Condition="'$(OS)' == 'Unix'">
3236
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
3337
</ItemGroup>
34-
38+
3539
</Project>

OpenFeature.sln

+28-7
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Tests", "test\O
7777
EndProject
7878
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Benchmarks", "test\OpenFeature.Benchmarks\OpenFeature.Benchmarks.csproj", "{90E7EAD3-251E-4490-AF78-E758E33518E5}"
7979
EndProject
80-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.E2ETests", "test\OpenFeature.E2ETests\OpenFeature.E2ETests.csproj", "{7398C446-2630-4F8C-9278-4E807720E9E5}"
80+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.E2ETests", "test\OpenFeature.E2ETests\OpenFeature.E2ETests.csproj", "{7398C446-2630-4F8C-9278-4E807720E9E5}"
81+
EndProject
82+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.DependencyInjection", "src\OpenFeature.DependencyInjection\OpenFeature.DependencyInjection.csproj", "{C5415057-2700-48B5-940A-7A10969FA639}"
83+
EndProject
84+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.DependencyInjection.Tests", "test\OpenFeature.DependencyInjection.Tests\OpenFeature.DependencyInjection.Tests.csproj", "{EB35F9F6-8A79-410E-A293-9387BC4AC9A7}"
85+
EndProject
86+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Hosting", "src\OpenFeature.Hosting\OpenFeature.Hosting.csproj", "{C99DA02A-3981-45A6-B3F8-4A1A48653DEE}"
8187
EndProject
8288
Global
8389
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -101,21 +107,36 @@ Global
101107
{7398C446-2630-4F8C-9278-4E807720E9E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
102108
{7398C446-2630-4F8C-9278-4E807720E9E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
103109
{7398C446-2630-4F8C-9278-4E807720E9E5}.Release|Any CPU.Build.0 = Release|Any CPU
110+
{C5415057-2700-48B5-940A-7A10969FA639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
111+
{C5415057-2700-48B5-940A-7A10969FA639}.Debug|Any CPU.Build.0 = Debug|Any CPU
112+
{C5415057-2700-48B5-940A-7A10969FA639}.Release|Any CPU.ActiveCfg = Release|Any CPU
113+
{C5415057-2700-48B5-940A-7A10969FA639}.Release|Any CPU.Build.0 = Release|Any CPU
114+
{EB35F9F6-8A79-410E-A293-9387BC4AC9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
115+
{EB35F9F6-8A79-410E-A293-9387BC4AC9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
116+
{EB35F9F6-8A79-410E-A293-9387BC4AC9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
117+
{EB35F9F6-8A79-410E-A293-9387BC4AC9A7}.Release|Any CPU.Build.0 = Release|Any CPU
118+
{C99DA02A-3981-45A6-B3F8-4A1A48653DEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
119+
{C99DA02A-3981-45A6-B3F8-4A1A48653DEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
120+
{C99DA02A-3981-45A6-B3F8-4A1A48653DEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
121+
{C99DA02A-3981-45A6-B3F8-4A1A48653DEE}.Release|Any CPU.Build.0 = Release|Any CPU
104122
EndGlobalSection
105123
GlobalSection(SolutionProperties) = preSolution
106124
HideSolutionNode = FALSE
107125
EndGlobalSection
108126
GlobalSection(NestedProjects) = preSolution
127+
{9392E03B-4E6B-434C-8553-B859424388B1} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
128+
{2B172AA0-A5A6-4D94-BA1F-B79D59B0C2D8} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
129+
{C4746B8C-FE19-440B-922C-C2377F906FE8} = {2B172AA0-A5A6-4D94-BA1F-B79D59B0C2D8}
130+
{09BAB3A2-E94C-490A-861C-7D1E11BB7024} = {2B172AA0-A5A6-4D94-BA1F-B79D59B0C2D8}
131+
{4BB69DB3-9653-4197-9589-37FA6D658CB7} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
132+
{72005F60-C2E8-40BF-AE95-893635134D7D} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
109133
{07A6E6BD-FB7E-4B3B-9CBE-65AE9D0EB223} = {C97E9975-E10A-4817-AE2C-4DD69C3C02D4}
110134
{49BB42BA-10A6-4DA3-A7D5-38C968D57837} = {65FBA159-23E0-4CF9-881B-F78DBFF198E9}
111135
{90E7EAD3-251E-4490-AF78-E758E33518E5} = {65FBA159-23E0-4CF9-881B-F78DBFF198E9}
112136
{7398C446-2630-4F8C-9278-4E807720E9E5} = {65FBA159-23E0-4CF9-881B-F78DBFF198E9}
113-
{C4746B8C-FE19-440B-922C-C2377F906FE8} = {2B172AA0-A5A6-4D94-BA1F-B79D59B0C2D8}
114-
{09BAB3A2-E94C-490A-861C-7D1E11BB7024} = {2B172AA0-A5A6-4D94-BA1F-B79D59B0C2D8}
115-
{72005F60-C2E8-40BF-AE95-893635134D7D} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
116-
{9392E03B-4E6B-434C-8553-B859424388B1} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
117-
{2B172AA0-A5A6-4D94-BA1F-B79D59B0C2D8} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
118-
{4BB69DB3-9653-4197-9589-37FA6D658CB7} = {E8916D4F-B97E-42D6-8620-ED410A106F94}
137+
{C5415057-2700-48B5-940A-7A10969FA639} = {C97E9975-E10A-4817-AE2C-4DD69C3C02D4}
138+
{EB35F9F6-8A79-410E-A293-9387BC4AC9A7} = {65FBA159-23E0-4CF9-881B-F78DBFF198E9}
139+
{C99DA02A-3981-45A6-B3F8-4A1A48653DEE} = {C97E9975-E10A-4817-AE2C-4DD69C3C02D4}
119140
EndGlobalSection
120141
GlobalSection(ExtensibilityGlobals) = postSolution
121142
SolutionGuid = {41F01B78-FB06-404F-8AD0-6ED6973F948F}

README.md

+76-1
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ public async Task Example()
7979
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
8080
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
8181
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
82+
| 🔬 | [DependencyInjection](#DependencyInjection) | Integrate OpenFeature with .NET's dependency injection for streamlined provider setup. |
8283

83-
> Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌
84+
> Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ | Experimental: 🔬
8485
8586
### Providers
8687

@@ -300,6 +301,80 @@ public class MyHook : Hook
300301

301302
Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=hook&projects=&template=document-hook.yaml&title=%5BHook%5D%3A+) so we can add it to the docs!
302303

304+
### DependencyInjection
305+
> [!NOTE]
306+
> The OpenFeature.DependencyInjection and OpenFeature.Hosting packages are currently experimental. They streamline the integration of OpenFeature within .NET applications, allowing for seamless configuration and lifecycle management of feature flag providers using dependency injection and hosting services.
307+
308+
#### Installation
309+
To set up dependency injection and hosting capabilities for OpenFeature, install the following packages:
310+
```sh
311+
dotnet add package OpenFeature.DependencyInjection
312+
dotnet add package OpenFeature.Hosting
313+
```
314+
#### Usage Examples
315+
For a basic configuration, you can use the InMemoryProvider. This provider is simple and well-suited for development and testing purposes.
316+
317+
**Basic Configuration:**
318+
```csharp
319+
builder.Services.AddOpenFeature(featureBuilder => {
320+
featureBuilder
321+
.AddHostedFeatureLifecycle() // From Hosting package
322+
.AddContext((contextBuilder, serviceProvider) => { /* Custom context configuration */ })
323+
.AddInMemoryProvider();
324+
});
325+
```
326+
**Domain-Scoped Provider Configuration:**
327+
<br />To set up multiple providers with a selection policy, define logic for choosing the default provider. This example designates `name1` as the default provider:
328+
```csharp
329+
builder.Services.AddOpenFeature(featureBuilder => {
330+
featureBuilder
331+
.AddHostedFeatureLifecycle()
332+
.AddContext((contextBuilder, serviceProvider) => { /* Custom context configuration */ })
333+
.AddInMemoryProvider("name1")
334+
.AddInMemoryProvider("name2")
335+
.AddPolicyName(options => {
336+
// Custom logic to select a default provider
337+
options.DefaultNameSelector = serviceProvider => "name1";
338+
});
339+
});
340+
```
341+
#### Creating a New Provider
342+
To integrate a custom provider, such as InMemoryProvider, you’ll need to create a factory that builds and configures the provider. This section demonstrates how to set up InMemoryProvider as a new provider with custom configuration options.
343+
344+
**Configuring InMemoryProvider as a New Provider**
345+
<br />Begin by creating a custom factory class, `InMemoryProviderFactory`, that implements `IFeatureProviderFactory`. This factory will initialize your provider with any necessary configurations.
346+
```csharp
347+
public class InMemoryProviderFactory : IFeatureProviderFactory
348+
{
349+
internal IDictionary<string, Flag>? Flags { get; set; }
350+
351+
public FeatureProvider Create() => new InMemoryProvider(Flags);
352+
}
353+
```
354+
**Adding an Extension Method to OpenFeatureBuilder**
355+
<br />To streamline the configuration process, add an extension method, `AddInMemoryProvider`, to `OpenFeatureBuilder`. This allows you to set up the provider with either a domain-scoped or a default configuration.
356+
357+
```csharp
358+
public static partial class FeatureBuilderExtensions
359+
{
360+
public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, Action<IDictionary<string, Flag>>? configure = null)
361+
=> builder.AddProvider<InMemoryProviderFactory>(factory => ConfigureFlags(factory, configure));
362+
363+
public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, string domain, Action<IDictionary<string, Flag>>? configure = null)
364+
=> builder.AddProvider<InMemoryProviderFactory>(domain, factory => ConfigureFlags(factory, configure));
365+
366+
private static void ConfigureFlags(InMemoryProviderFactory factory, Action<IDictionary<string, Flag>>? configure)
367+
{
368+
if (configure == null)
369+
return;
370+
371+
var flag = new Dictionary<string, Flag>();
372+
configure.Invoke(flag);
373+
factory.Flags = flag;
374+
}
375+
}
376+
```
377+
303378
<!-- x-hide-in-docs-start -->
304379
## ⭐️ Support the project
305380

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace OpenFeature.DependencyInjection.Diagnostics;
2+
3+
/// <summary>
4+
/// Contains identifiers for experimental features and diagnostics in the OpenFeature framework.
5+
/// </summary>
6+
/// <remarks>
7+
/// <c>Experimental</c> - This class includes identifiers that allow developers to track and conditionally enable
8+
/// experimental features. Each identifier follows a structured code format to indicate the feature domain,
9+
/// maturity level, and unique identifier. Note that experimental features are subject to change or removal
10+
/// in future releases.
11+
/// <para>
12+
/// <strong>Basic Information</strong><br/>
13+
/// These identifiers conform to OpenFeature’s Diagnostics Specifications, allowing developers to recognize
14+
/// and manage experimental features effectively.
15+
/// </para>
16+
/// </remarks>
17+
/// <example>
18+
/// <code>
19+
/// Code Structure:
20+
/// - "OF" - Represents the OpenFeature library.
21+
/// - "DI" - Indicates the Dependency Injection domain.
22+
/// - "001" - Unique identifier for a specific feature.
23+
/// </code>
24+
/// </example>
25+
internal static class FeatureCodes
26+
{
27+
/// <summary>
28+
/// Identifier for the experimental Dependency Injection features within the OpenFeature framework.
29+
/// </summary>
30+
/// <remarks>
31+
/// <c>OFDI001</c> identifier marks experimental features in the Dependency Injection (DI) domain.
32+
///
33+
/// Usage:
34+
/// Developers can use this identifier to conditionally enable or test experimental DI features.
35+
/// It is part of the OpenFeature diagnostics system to help track experimental functionality.
36+
/// </remarks>
37+
public const string NewDi = "OFDI001";
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Diagnostics;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace OpenFeature.DependencyInjection;
5+
6+
[DebuggerStepThrough]
7+
internal static class Guard
8+
{
9+
public static void ThrowIfNull(object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
10+
{
11+
if (argument is null)
12+
throw new ArgumentNullException(paramName);
13+
}
14+
15+
public static void ThrowIfNullOrWhiteSpace(string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
16+
{
17+
if (string.IsNullOrWhiteSpace(argument))
18+
throw new ArgumentNullException(paramName);
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace OpenFeature.DependencyInjection;
2+
3+
/// <summary>
4+
/// Defines the contract for managing the lifecycle of a feature api.
5+
/// </summary>
6+
public interface IFeatureLifecycleManager
7+
{
8+
/// <summary>
9+
/// Ensures that the feature provider is properly initialized and ready to be used.
10+
/// This method should handle all necessary checks, configuration, and setup required to prepare the feature provider.
11+
/// </summary>
12+
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
13+
/// <returns>A Task representing the asynchronous operation of initializing the feature provider.</returns>
14+
/// <exception cref="InvalidOperationException">Thrown when the feature provider is not registered or is in an invalid state.</exception>
15+
ValueTask EnsureInitializedAsync(CancellationToken cancellationToken = default);
16+
17+
/// <summary>
18+
/// Gracefully shuts down the feature api, ensuring all resources are properly disposed of and any persistent state is saved.
19+
/// This method should handle all necessary cleanup and shutdown operations for the feature provider.
20+
/// </summary>
21+
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
22+
/// <returns>A Task representing the asynchronous operation of shutting down the feature provider.</returns>
23+
ValueTask ShutdownAsync(CancellationToken cancellationToken = default);
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace OpenFeature.DependencyInjection;
2+
3+
/// <summary>
4+
/// Provides a contract for creating instances of <see cref="FeatureProvider"/>.
5+
/// This factory interface enables custom configuration and initialization of feature providers
6+
/// to support domain-specific or application-specific feature flag management.
7+
/// </summary>
8+
#if NET8_0_OR_GREATER
9+
[System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)]
10+
#endif
11+
public interface IFeatureProviderFactory
12+
{
13+
/// <summary>
14+
/// Creates an instance of a <see cref="FeatureProvider"/> configured according to
15+
/// the specific settings implemented by the concrete factory.
16+
/// </summary>
17+
/// <returns>
18+
/// A new instance of <see cref="FeatureProvider"/>.
19+
/// The configuration and behavior of this provider instance are determined by
20+
/// the implementation of this method.
21+
/// </returns>
22+
FeatureProvider Create();
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
3+
using Microsoft.Extensions.Options;
4+
5+
namespace OpenFeature.DependencyInjection.Internal;
6+
7+
internal sealed partial class FeatureLifecycleManager : IFeatureLifecycleManager
8+
{
9+
private readonly Api _featureApi;
10+
private readonly IServiceProvider _serviceProvider;
11+
private readonly ILogger<FeatureLifecycleManager> _logger;
12+
13+
public FeatureLifecycleManager(Api featureApi, IServiceProvider serviceProvider, ILogger<FeatureLifecycleManager> logger)
14+
{
15+
_featureApi = featureApi;
16+
_serviceProvider = serviceProvider;
17+
_logger = logger;
18+
}
19+
20+
/// <inheritdoc />
21+
public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToken = default)
22+
{
23+
this.LogStartingInitializationOfFeatureProvider();
24+
25+
var options = _serviceProvider.GetRequiredService<IOptions<OpenFeatureOptions>>().Value;
26+
if (options.HasDefaultProvider)
27+
{
28+
var featureProvider = _serviceProvider.GetRequiredService<FeatureProvider>();
29+
await _featureApi.SetProviderAsync(featureProvider).ConfigureAwait(false);
30+
}
31+
32+
foreach (var name in options.ProviderNames)
33+
{
34+
var featureProvider = _serviceProvider.GetRequiredKeyedService<FeatureProvider>(name);
35+
await _featureApi.SetProviderAsync(name, featureProvider).ConfigureAwait(false);
36+
}
37+
}
38+
39+
/// <inheritdoc />
40+
public async ValueTask ShutdownAsync(CancellationToken cancellationToken = default)
41+
{
42+
this.LogShuttingDownFeatureProvider();
43+
await _featureApi.ShutdownAsync().ConfigureAwait(false);
44+
}
45+
46+
[LoggerMessage(200, LogLevel.Information, "Starting initialization of the feature provider")]
47+
partial void LogStartingInitializationOfFeatureProvider();
48+
49+
[LoggerMessage(200, LogLevel.Information, "Shutting down the feature provider")]
50+
partial void LogShuttingDownFeatureProvider();
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @formatter:off
2+
// ReSharper disable All
3+
#if NETCOREAPP3_0_OR_GREATER
4+
// https://github.com/dotnet/runtime/issues/96197
5+
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))]
6+
#else
7+
#pragma warning disable
8+
// Licensed to the .NET Foundation under one or more agreements.
9+
// The .NET Foundation licenses this file to you under the MIT license.
10+
11+
namespace System.Runtime.CompilerServices;
12+
13+
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
14+
internal sealed class CallerArgumentExpressionAttribute : Attribute
15+
{
16+
public CallerArgumentExpressionAttribute(string parameterName)
17+
{
18+
ParameterName = parameterName;
19+
}
20+
21+
public string ParameterName { get; }
22+
}
23+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @formatter:off
2+
// ReSharper disable All
3+
#if NET5_0_OR_GREATER
4+
// https://github.com/dotnet/runtime/issues/96197
5+
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))]
6+
#else
7+
#pragma warning disable
8+
// Licensed to the .NET Foundation under one or more agreements.
9+
// The .NET Foundation licenses this file to you under the MIT license.
10+
11+
using System.ComponentModel;
12+
13+
namespace System.Runtime.CompilerServices;
14+
15+
/// <summary>
16+
/// Reserved to be used by the compiler for tracking metadata.
17+
/// This class should not be used by developers in source code.
18+
/// </summary>
19+
[EditorBrowsable(EditorBrowsableState.Never)]
20+
static class IsExternalInit { }
21+
#endif

0 commit comments

Comments
 (0)