Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInjection (#79425)
authorEric Erhardt <eric.erhardt@microsoft.com>
Tue, 10 Jan 2023 19:57:11 +0000 (13:57 -0600)
committerGitHub <noreply@github.com>
Tue, 10 Jan 2023 19:57:11 +0000 (13:57 -0600)
* Remove RequiresDynamicCode from Microsoft.Extensions.DependencyInjection

We need a better approach in order to support applications that use DependencyInjection and publish for NativeAOT. DependencyInjection needs to have reliable behavior before and after publishing for NativeAOT. The application can't work successfully at development-time, but then fail after publishing with PublishAot=true.

We will resolve the 2 NativeAOT warnings above by adding a runtime check that is behind the new AppContext switch added in https://github.com/dotnet/runtime/pull/80246 (`System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported`). The runtime check ensures the Types being used with Enumerable and Open Generic services are only Reference Types. If an application tries to create an Enumerable or Closed Generic service of a ValueType, DependencyInjection will throw an exception. The check is enabled by default when PublishAot=true.

Fix #79286

25 files changed:
src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs
src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj
src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.cs
src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.csproj
src/libraries/Microsoft.Extensions.Hosting/src/Host.cs
src/libraries/Microsoft.Extensions.Hosting/src/HostApplicationBuilder.cs
src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs
src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs
src/libraries/Microsoft.Extensions.Hosting/src/Microsoft.Extensions.Hosting.csproj
src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs
src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj
src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs
src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj

index 92a1593..0b85156 100644 (file)
@@ -6,7 +6,6 @@
 
 namespace Microsoft.Extensions.DependencyInjection
 {
-    [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
     public partial class DefaultServiceProviderFactory : Microsoft.Extensions.DependencyInjection.IServiceProviderFactory<Microsoft.Extensions.DependencyInjection.IServiceCollection>
     {
         public DefaultServiceProviderFactory() { }
@@ -16,11 +15,8 @@ namespace Microsoft.Extensions.DependencyInjection
     }
     public static partial class ServiceCollectionContainerBuilderExtensions
     {
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
         public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
         public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")]
         public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; }
     }
     public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider
index 1be6963..522569c 100644 (file)
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.DependencyInjection.Abstractions\ref\Microsoft.Extensions.DependencyInjection.Abstractions.csproj" />
   </ItemGroup>
 
-  <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
-    <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
-  </ItemGroup>
-
   <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">
     <PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsVersion)" />
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Bcl.AsyncInterfaces\ref\Microsoft.Bcl.AsyncInterfaces.csproj" />
index 13863a4..7fe53e3 100644 (file)
@@ -2,14 +2,12 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
-using System.Diagnostics.CodeAnalysis;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
     /// <summary>
     /// Default implementation of <see cref="IServiceProviderFactory{TContainerBuilder}"/>.
     /// </summary>
-    [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
     public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
     {
         private readonly ServiceProviderOptions _options;
index efbb7cc..a81b637 100644 (file)
   <data name="TrimmingAnnotationsDoNotMatch_NewConstraint" xml:space="preserve">
     <value>Generic implementation type '{0}' has a DefaultConstructorConstraint ('new()' constraint), but the generic service type '{1}' doesn't.</value>
   </data>
-</root>
+  <data name="AotCannotCreateEnumerableValueType" xml:space="preserve">
+    <value>Unable to create an Enumerable service of type '{0}' because it is a ValueType. Native code to support creating Enumerable services might not be available with native AOT.</value>
+  </data>
+  <data name="AotCannotCreateGenericValueType" xml:space="preserve">
+    <value>Unable to create a generic service for type '{0}' because '{1}' is a ValueType. Native code to support creating generic services might not be available with native AOT.</value>
+  </data>
+</root>
\ No newline at end of file
index 1cde6c4..5b12c82 100644 (file)
@@ -2,9 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using Microsoft.Extensions.DependencyInjection.ServiceLookup;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -18,8 +15,6 @@ namespace Microsoft.Extensions.DependencyInjection
         /// </summary>
         /// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param>
         /// <returns>The <see cref="ServiceProvider"/>.</returns>
-
-        [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
         public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
         {
             return BuildServiceProvider(services, ServiceProviderOptions.Default);
@@ -34,7 +29,6 @@ namespace Microsoft.Extensions.DependencyInjection
         /// <c>true</c> to perform check verifying that scoped services never gets resolved from root provider; otherwise <c>false</c>.
         /// </param>
         /// <returns>The <see cref="ServiceProvider"/>.</returns>
-        [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
         public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
         {
             return services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes });
@@ -49,7 +43,6 @@ namespace Microsoft.Extensions.DependencyInjection
         /// Configures various service provider behaviors.
         /// </param>
         /// <returns>The <see cref="ServiceProvider"/>.</returns>
-        [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
         public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
         {
             if (services is null)
index 22b3d4f..2e8527f 100644 (file)
@@ -11,7 +11,6 @@ using Microsoft.Extensions.Internal;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
     internal sealed class CallSiteFactory : IServiceProviderIsService
     {
         private const int DefaultSlot = 0;
@@ -244,8 +243,14 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                 {
                     Type itemType = serviceType.GenericTypeArguments[0];
-                    CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root;
+                    if (ServiceProvider.VerifyAotCompatibility && itemType.IsValueType)
+                    {
+                        // NativeAOT apps are not able to make Enumerable of ValueType services
+                        // since there is no guarantee the ValueType[] code has been generated.
+                        throw new InvalidOperationException(SR.Format(SR.AotCannotCreateEnumerableValueType, itemType));
+                    }
 
+                    CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root;
                     var callSites = new List<ServiceCallSite>();
 
                     // If item type is not generic we can safely use descriptor cache
@@ -350,6 +355,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             Justification = "MakeGenericType here is used to create a closed generic implementation type given the closed service type. " +
             "Trimming annotations on the generic types are verified when 'Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability' is set, which is set by default when PublishTrimmed=true. " +
             "That check informs developers when these generic types don't have compatible trimming annotations.")]
+        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
+            Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
+            "this method ensures the generic types being created aren't using ValueTypes.")]
         private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
         {
             if (serviceType.IsConstructedGenericType &&
@@ -366,7 +374,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 Type closedType;
                 try
                 {
-                    closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
+                    Type[] genericTypeArguments = serviceType.GenericTypeArguments;
+                    if (ServiceProvider.VerifyAotCompatibility)
+                    {
+                        VerifyOpenGenericAotCompatibility(serviceType, genericTypeArguments);
+                    }
+
+                    closedType = descriptor.ImplementationType.MakeGenericType(genericTypeArguments);
                 }
                 catch (ArgumentException)
                 {
@@ -524,6 +538,24 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             return parameterCallSites;
         }
 
+        /// <summary>
+        /// Verifies none of the generic type arguments are ValueTypes.
+        /// </summary>
+        /// <remarks>
+        /// NativeAOT apps are not guaranteed that the native code for the closed generic of ValueType
+        /// has been generated. To catch these problems early, this verification is enabled at development-time
+        /// to inform the developer early that this scenario will not work once AOT'd.
+        /// </remarks>
+        private static void VerifyOpenGenericAotCompatibility(Type serviceType, Type[] genericTypeArguments)
+        {
+            foreach (Type typeArg in genericTypeArguments)
+            {
+                if (typeArg.IsValueType)
+                {
+                    throw new InvalidOperationException(SR.Format(SR.AotCannotCreateGenericValueType, serviceType, typeArg));
+                }
+            }
+        }
 
         public void Add(Type type, ServiceCallSite serviceCallSite)
         {
index f41e182..08c74b1 100644 (file)
@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Runtime.ExceptionServices;
@@ -10,7 +11,6 @@ using System.Threading;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
     internal sealed class CallSiteRuntimeResolver : CallSiteVisitor<RuntimeResolverContext, object?>
     {
         public static CallSiteRuntimeResolver Instance { get; } = new();
@@ -162,7 +162,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
         protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
         {
-            var array = Array.CreateInstance(
+            Array array = CreateArray(
                 enumerableCallSite.ItemType,
                 enumerableCallSite.ServiceCallSites.Length);
 
@@ -172,6 +172,15 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 array.SetValue(value, index);
             }
             return array;
+
+            [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
+                Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")]
+            static Array CreateArray(Type elementType, int length)
+            {
+                Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType.");
+
+                return Array.CreateInstance(elementType, length);
+            }
         }
 
         protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
index 45fe2c3..d7aa44d 100644 (file)
@@ -8,12 +8,12 @@ using System.Threading;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
     internal sealed class DynamicServiceProviderEngine : CompiledServiceProviderEngine
     {
         private readonly ServiceProvider _serviceProvider;
 
-        public DynamicServiceProviderEngine(ServiceProvider serviceProvider): base(serviceProvider)
+        [RequiresDynamicCode("Creates DynamicMethods")]
+        public DynamicServiceProviderEngine(ServiceProvider serviceProvider) : base(serviceProvider)
         {
             _serviceProvider = serviceProvider;
         }
index c8d4b7a..af1cf57 100644 (file)
@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Linq.Expressions;
@@ -11,7 +12,6 @@ using System.Reflection;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
     internal sealed class ExpressionResolverBuilder : CallSiteVisitor<object?, Expression>
     {
         private static readonly ParameterExpression ScopeParameter = Expression.Parameter(typeof(ServiceProviderEngineScope));
@@ -114,10 +114,19 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
         protected override Expression VisitIEnumerable(IEnumerableCallSite callSite, object? context)
         {
+            [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
+                Justification = "VerifyAotCompatibility ensures elementType is not a ValueType")]
+            static MethodInfo GetArrayEmptyMethodInfo(Type elementType)
+            {
+                Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !elementType.IsValueType, "VerifyAotCompatibility=true will throw during building the IEnumerableCallSite if elementType is a ValueType.");
+
+                return ServiceLookupHelpers.GetArrayEmptyMethodInfo(elementType);
+            }
+
             if (callSite.ServiceCallSites.Length == 0)
             {
                 return Expression.Constant(
-                    ServiceLookupHelpers.GetArrayEmptyMethodInfo(callSite.ItemType)
+                    GetArrayEmptyMethodInfo(callSite.ItemType)
                     .Invoke(obj: null, parameters: Array.Empty<object>()));
             }
 
index d179cb6..8298bd3 100644 (file)
@@ -10,7 +10,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
     {
         private readonly ExpressionResolverBuilder _expressionResolverBuilder;
 
-        [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
         public ExpressionsServiceProviderEngine(ServiceProvider serviceProvider)
         {
             _expressionResolverBuilder = new ExpressionResolverBuilder(serviceProvider);
index 5774a2c..7565758 100644 (file)
@@ -3,11 +3,11 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
     internal sealed class IEnumerableCallSite : ServiceCallSite
     {
         internal Type ItemType { get; }
@@ -15,12 +15,22 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
         public IEnumerableCallSite(ResultCache cache, Type itemType, ServiceCallSite[] serviceCallSites) : base(cache)
         {
+            Debug.Assert(!ServiceProvider.VerifyAotCompatibility || !itemType.IsValueType, "If VerifyAotCompatibility=true, an IEnumerableCallSite should not be created with a ValueType.");
+
             ItemType = itemType;
             ServiceCallSites = serviceCallSites;
         }
 
+        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
+            Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
+            "CallSiteFactory ensures ItemType is not a ValueType.")]
         public override Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType);
-        public override Type ImplementationType  => ItemType.MakeArrayType();
+
+        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
+            Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
+            "CallSiteFactory ensures ItemType is not a ValueType.")]
+        public override Type ImplementationType => ItemType.MakeArrayType();
+
         public override CallSiteKind Kind { get; } = CallSiteKind.IEnumerable;
     }
 }
index b25e7f8..c8d099c 100644 (file)
@@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)]
     internal sealed class RuntimeServiceProviderEngine : ServiceProviderEngine
     {
         public static RuntimeServiceProviderEngine Instance { get; } = new RuntimeServiceProviderEngine();
index dd9b1af..5954d08 100644 (file)
@@ -16,8 +16,6 @@ namespace Microsoft.Extensions.DependencyInjection
     /// </summary>
     public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDisposable
     {
-        internal const string RequiresDynamicCodeMessage = "Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.";
-
         private readonly CallSiteValidator? _callSiteValidator;
 
         private readonly Func<Type, Func<ServiceProviderEngineScope, object?>> _createServiceAccessor;
@@ -36,7 +34,13 @@ namespace Microsoft.Extensions.DependencyInjection
         internal static bool VerifyOpenGenericServiceTrimmability { get; } =
             AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability", out bool verifyOpenGenerics) ? verifyOpenGenerics : false;
 
-        [RequiresDynamicCode(RequiresDynamicCodeMessage)]
+        internal static bool VerifyAotCompatibility =>
+#if NETFRAMEWORK || NETSTANDARD2_0
+            false;
+#else
+            !RuntimeFeature.IsDynamicCodeSupported;
+#endif
+
         internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
         {
             // note that Root needs to be set before calling GetEngine(), because the engine may need to access Root
@@ -157,7 +161,6 @@ namespace Microsoft.Extensions.DependencyInjection
             }
         }
 
-        [RequiresDynamicCode(RequiresDynamicCodeMessage)]
         private Func<ServiceProviderEngineScope, object?> CreateServiceAccessor(Type serviceType)
         {
             ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
@@ -194,17 +197,16 @@ namespace Microsoft.Extensions.DependencyInjection
             return new ServiceProviderEngineScope(this, isRootScope: false);
         }
 
-        [RequiresDynamicCode(RequiresDynamicCodeMessage)]
         private ServiceProviderEngine GetEngine()
         {
             ServiceProviderEngine engine;
 
 #if NETFRAMEWORK || NETSTANDARD2_0
-            engine = new DynamicServiceProviderEngine(this);
+            engine = CreateDynamicEngine();
 #else
             if (RuntimeFeature.IsDynamicCodeCompiled)
             {
-                engine = new DynamicServiceProviderEngine(this);
+                engine = CreateDynamicEngine();
             }
             else
             {
@@ -213,6 +215,10 @@ namespace Microsoft.Extensions.DependencyInjection
             }
 #endif
             return engine;
+
+            [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
+                Justification = "CreateDynamicEngine won't be called when using NativeAOT.")] // see also https://github.com/dotnet/linker/issues/2715
+            ServiceProviderEngine CreateDynamicEngine() => new DynamicServiceProviderEngine(this);
         }
     }
 }
index c304f19..bef850f 100644 (file)
@@ -919,6 +919,84 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             }, options);
         }
 
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] // RuntimeConfigurationOptions are not supported on .NET Framework (and neither is NativeAOT)
+        public void VerifyDynamicCodeNotSupportedChecks()
+        {
+            Func<Type, ServiceCallSite> CreateAotCompatibilityCallSiteFactory()
+            {
+                ServiceDescriptor[] descriptors = new[]
+                {
+                    new ServiceDescriptor(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>), ServiceLifetime.Transient),
+                    new ServiceDescriptor(typeof(IServiceWithTwoGenerics<,>), typeof(ServiceWithTwoGenericsValid<,>), ServiceLifetime.Transient),
+
+                    new ServiceDescriptor(typeof(Struct1), new Struct1(1)),
+                    new ServiceDescriptor(typeof(Struct1), new Struct1(2)),
+                };
+
+                return GetCallSiteFactory(descriptors);
+            }
+
+            RemoteInvokeOptions options = new RemoteInvokeOptions();
+            options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false");
+
+            using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(() =>
+            {
+                Func<Type, ServiceCallSite> callSiteFactory = CreateAotCompatibilityCallSiteFactory();
+
+                // Verify open generics throw when passing ValueTypes
+                Assert.Throws<InvalidOperationException>(() => callSiteFactory(typeof(IFakeOpenGenericService<Struct1>)));
+                Assert.Throws<InvalidOperationException>(() => callSiteFactory(typeof(IFakeOpenGenericService<int>)));
+                Assert.Throws<InvalidOperationException>(() => callSiteFactory(typeof(IServiceWithTwoGenerics<Class3, int>)));
+                Assert.Throws<InvalidOperationException>(() => callSiteFactory(typeof(IServiceWithTwoGenerics<int, Class3>)));
+
+                ServiceCallSite callSite = callSiteFactory(typeof(IFakeOpenGenericService<Class3>));
+                Assert.Equal(CallSiteKind.Constructor, callSite.Kind);
+                Assert.Equal(typeof(ClassWithNoConstraints<Class3>), callSite.ImplementationType);
+
+                callSite = callSiteFactory(typeof(IServiceWithTwoGenerics<Class3, Class3>));
+                Assert.Equal(CallSiteKind.Constructor, callSite.Kind);
+                Assert.Equal(typeof(ServiceWithTwoGenericsValid<Class3, Class3>), callSite.ImplementationType);
+
+                // Verify Enumerable services throw when passing ValueTypes
+                Assert.Throws<InvalidOperationException>(() => callSiteFactory(typeof(IEnumerable<Struct1>)));
+
+                callSite = callSiteFactory(typeof(Struct1));
+                Assert.Equal(CallSiteKind.Constant, callSite.Kind);
+                Assert.Equal(2, ((Struct1)callSite.Value).Value);
+            }, options);
+
+            // Verify the above scenarios work when IsDynamicCodeSupported is not set
+            Func<Type, ServiceCallSite> callSiteFactory = CreateAotCompatibilityCallSiteFactory();
+
+            // Open Generics
+            ServiceCallSite callSite = callSiteFactory(typeof(IFakeOpenGenericService<Struct1>));
+            Assert.Equal(CallSiteKind.Constructor, callSite.Kind);
+            Assert.Equal(typeof(ClassWithNoConstraints<Struct1>), callSite.ImplementationType);
+
+            callSite = callSiteFactory(typeof(IFakeOpenGenericService<int>));
+            Assert.Equal(CallSiteKind.Constructor, callSite.Kind);
+            Assert.Equal(typeof(ClassWithNoConstraints<int>), callSite.ImplementationType);
+
+            callSite = callSiteFactory(typeof(IServiceWithTwoGenerics<Class3, int>));
+            Assert.Equal(CallSiteKind.Constructor, callSite.Kind);
+            Assert.Equal(typeof(ServiceWithTwoGenericsValid<Class3, int>), callSite.ImplementationType);
+
+            callSite = callSiteFactory(typeof(IServiceWithTwoGenerics<int, Class3>));
+            Assert.Equal(CallSiteKind.Constructor, callSite.Kind);
+            Assert.Equal(typeof(ServiceWithTwoGenericsValid<int, Class3>), callSite.ImplementationType);
+
+            // Enumerable 
+            callSite = callSiteFactory(typeof(IEnumerable<Struct1>));
+            Assert.Equal(CallSiteKind.IEnumerable, callSite.Kind);
+            IEnumerableCallSite enumerableCallSite = (IEnumerableCallSite)callSite;
+            Assert.Equal(2, enumerableCallSite.ServiceCallSites.Length);
+            Assert.Equal(CallSiteKind.Constant, enumerableCallSite.ServiceCallSites[0].Kind);
+            Assert.Equal(1, ((Struct1)enumerableCallSite.ServiceCallSites[0].Value).Value);
+            Assert.Equal(CallSiteKind.Constant, enumerableCallSite.ServiceCallSites[1].Kind);
+            Assert.Equal(2, ((Struct1)enumerableCallSite.ServiceCallSites[1].Value).Value);
+        }
+
         private static Func<Type, ServiceCallSite> GetCallSiteFactory(params ServiceDescriptor[] descriptors)
         {
             var collection = new ServiceCollection();
@@ -944,13 +1022,14 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     c.GetParameters().Select(p => p.ParameterType),
                     parameterTypes));
 
-
         private class Class1 { public Class1(Class2 c2) { } }
         private class Class2 { public Class2(Class3 c3) { } }
         private class Class3 { }
         private class Class4 { public Class4(Class3 c3) { } }
         private class Class5 { public Class5(Class2 c2) { } }
 
+        private record struct Struct1(int Value) { }
+
         // Open generic
         private class ClassA { public ClassA(ClassB cb) { } }
         private class ClassB { public ClassB(ClassC<object> cc) { } }
index 7910670..cc1d7d8 100644 (file)
@@ -25,22 +25,15 @@ namespace Microsoft.Extensions.Hosting
     }
     public static partial class Host
     {
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Hosting.HostApplicationBuilder CreateApplicationBuilder() { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Hosting.HostApplicationBuilder CreateApplicationBuilder(string[]? args) { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Hosting.IHostBuilder CreateDefaultBuilder() { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Hosting.IHostBuilder CreateDefaultBuilder(string[]? args) { throw null; }
     }
     public sealed partial class HostApplicationBuilder
     {
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public HostApplicationBuilder() { }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public HostApplicationBuilder(Microsoft.Extensions.Hosting.HostApplicationBuilderSettings? settings) { }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public HostApplicationBuilder(string[]? args) { }
         public Microsoft.Extensions.Configuration.ConfigurationManager Configuration { get { throw null; } }
         public Microsoft.Extensions.Hosting.IHostEnvironment Environment { get { throw null; } }
@@ -61,7 +54,6 @@ namespace Microsoft.Extensions.Hosting
     }
     public partial class HostBuilder : Microsoft.Extensions.Hosting.IHostBuilder
     {
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public HostBuilder() { }
         public System.Collections.Generic.IDictionary<object, object> Properties { get { throw null; } }
         public Microsoft.Extensions.Hosting.IHost Build() { throw null; }
@@ -76,7 +68,6 @@ namespace Microsoft.Extensions.Hosting
     {
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureAppConfiguration(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Configuration.IConfigurationBuilder> configureDelegate) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureContainer<TContainerBuilder>(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<TContainerBuilder> configureDelegate) { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureDefaults(this Microsoft.Extensions.Hosting.IHostBuilder builder, string[]? args) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureHostOptions(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Hosting.HostBuilderContext, Microsoft.Extensions.Hosting.HostOptions> configureOptions) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureHostOptions(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Hosting.HostOptions> configureOptions) { throw null; }
@@ -104,9 +95,7 @@ namespace Microsoft.Extensions.Hosting
         [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
         public static Microsoft.Extensions.Hosting.IHostBuilder UseConsoleLifetime(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Hosting.ConsoleLifetimeOptions> configureOptions) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder UseContentRoot(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, string contentRoot) { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Hosting.IHostBuilder UseDefaultServiceProvider(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.DependencyInjection.ServiceProviderOptions> configure) { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Hosting.IHostBuilder UseDefaultServiceProvider(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Hosting.HostBuilderContext, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions> configure) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder UseEnvironment(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, string environment) { throw null; }
     }
index 663242e..634b13f 100644 (file)
     <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
   </ItemGroup>
 
-  <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
-    <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
-  </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration\ref\Microsoft.Extensions.Configuration.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration.Abstractions\ref\Microsoft.Extensions.Configuration.Abstractions.csproj" />
index 37c1632..a836ec1 100644 (file)
@@ -1,7 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
@@ -13,8 +12,6 @@ namespace Microsoft.Extensions.Hosting
     /// </summary>
     public static class Host
     {
-        internal const string RequiresDynamicCodeMessage = "Hosting uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.";
-
         /// <summary>
         /// Initializes a new instance of the <see cref="HostBuilder"/> class with pre-configured defaults.
         /// </summary>
@@ -31,7 +28,6 @@ namespace Microsoft.Extensions.Hosting
         ///   </list>
         /// </remarks>
         /// <returns>The initialized <see cref="IHostBuilder"/>.</returns>
-        [RequiresDynamicCode(RequiresDynamicCodeMessage)]
         public static IHostBuilder CreateDefaultBuilder() =>
             CreateDefaultBuilder(args: null);
 
@@ -54,7 +50,6 @@ namespace Microsoft.Extensions.Hosting
         /// </remarks>
         /// <param name="args">The command line args.</param>
         /// <returns>The initialized <see cref="IHostBuilder"/>.</returns>
-        [RequiresDynamicCode(RequiresDynamicCodeMessage)]
         public static IHostBuilder CreateDefaultBuilder(string[]? args)
         {
             HostBuilder builder = new();
@@ -78,7 +73,6 @@ namespace Microsoft.Extensions.Hosting
         ///     <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item>
         ///   </list>
         /// </remarks>
-        [RequiresDynamicCode(RequiresDynamicCodeMessage)]
         public static HostApplicationBuilder CreateApplicationBuilder() => new HostApplicationBuilder();
 
         /// <summary>
@@ -99,7 +93,6 @@ namespace Microsoft.Extensions.Hosting
         ///   </list>
         /// </remarks>
         /// <param name="args">The command line args.</param>
-        [RequiresDynamicCode(RequiresDynamicCodeMessage)]
         public static HostApplicationBuilder CreateApplicationBuilder(string[]? args) => new HostApplicationBuilder(args);
     }
 }
index 9962638..f24e864 100644 (file)
@@ -4,7 +4,6 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
@@ -46,7 +45,6 @@ namespace Microsoft.Extensions.Hosting
         ///     <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item>
         ///   </list>
         /// </remarks>
-        [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
         public HostApplicationBuilder()
             : this(args: null)
         {
@@ -70,7 +68,6 @@ namespace Microsoft.Extensions.Hosting
         ///   </list>
         /// </remarks>
         /// <param name="args">The command line args.</param>
-        [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
         public HostApplicationBuilder(string[]? args)
             : this(new HostApplicationBuilderSettings { Args = args })
         {
@@ -80,7 +77,6 @@ namespace Microsoft.Extensions.Hosting
         /// Initializes a new instance of the <see cref="HostApplicationBuilder"/>.
         /// </summary>
         /// <param name="settings">Settings controlling initial configuration and whether default settings should be used.</param>
-        [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
         public HostApplicationBuilder(HostApplicationBuilderSettings? settings)
         {
             settings ??= new HostApplicationBuilderSettings();
index 4b1aa7d..4c015c5 100644 (file)
@@ -41,7 +41,6 @@ namespace Microsoft.Extensions.Hosting
         /// <summary>
         /// Initializes a new instance of <see cref="HostBuilder"/>.
         /// </summary>
-        [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
         public HostBuilder()
         {
             _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
index a22f5bf..c3f949a 100644 (file)
@@ -69,7 +69,6 @@ namespace Microsoft.Extensions.Hosting
         /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
         /// <param name="configure">The delegate that configures the <see cref="IServiceProvider"/>.</param>
         /// <returns>The <see cref="IHostBuilder"/>.</returns>
-        [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
         public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuilder, Action<ServiceProviderOptions> configure)
             => hostBuilder.UseDefaultServiceProvider((context, options) => configure(options));
 
@@ -79,7 +78,6 @@ namespace Microsoft.Extensions.Hosting
         /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
         /// <param name="configure">The delegate that configures the <see cref="IServiceProvider"/>.</param>
         /// <returns>The <see cref="IHostBuilder"/>.</returns>
-        [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
         public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuilder, Action<HostBuilderContext, ServiceProviderOptions> configure)
         {
             return hostBuilder.UseServiceProviderFactory(context =>
@@ -192,7 +190,6 @@ namespace Microsoft.Extensions.Hosting
         /// <param name="builder">The existing builder to configure.</param>
         /// <param name="args">The command line args.</param>
         /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
-        [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
         public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[]? args)
         {
             return builder.ConfigureHostConfiguration(config => ApplyDefaultHostConfiguration(config, args))
index 5c15d36..592f9e6 100644 (file)
     <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
   </ItemGroup>
 
-  <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
-    <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
-  </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration\src\Microsoft.Extensions.Configuration.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration.Abstractions\src\Microsoft.Extensions.Configuration.Abstractions.csproj" />
index dd65f6f..5b7fade 100644 (file)
@@ -61,7 +61,6 @@ namespace Microsoft.Extensions.Logging
         public LoggerFactory(System.Collections.Generic.IEnumerable<Microsoft.Extensions.Logging.ILoggerProvider> providers, Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Extensions.Logging.LoggerFilterOptions> filterOption, Microsoft.Extensions.Options.IOptions<Microsoft.Extensions.Logging.LoggerFactoryOptions>? options = null, Microsoft.Extensions.Logging.IExternalScopeProvider? scopeProvider = null) { }
         public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { }
         protected virtual bool CheckDisposed() { throw null; }
-        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("LoggerFactory.Create uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static Microsoft.Extensions.Logging.ILoggerFactory Create(System.Action<Microsoft.Extensions.Logging.ILoggingBuilder> configure) { throw null; }
         public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { throw null; }
         public void Dispose() { }
index 307fd01..9ded83f 100644 (file)
@@ -7,10 +7,6 @@
     <Compile Include="Microsoft.Extensions.Logging.cs" />
   </ItemGroup>
 
-  <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
-    <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
-  </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.DependencyInjection.Abstractions\ref\Microsoft.Extensions.DependencyInjection.Abstractions.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Logging.Abstractions\ref\Microsoft.Extensions.Logging.Abstractions.csproj" />
index 39651be..315ea91 100644 (file)
@@ -103,7 +103,6 @@ namespace Microsoft.Extensions.Logging
         /// </summary>
         /// <param name="configure">A delegate to configure the <see cref="ILoggingBuilder"/>.</param>
         /// <returns>The <see cref="ILoggerFactory"/> that was created.</returns>
-        [RequiresDynamicCode("LoggerFactory.Create uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")]
         public static ILoggerFactory Create(Action<ILoggingBuilder> configure)
         {
             var serviceCollection = new ServiceCollection();
index d832ce5..6d161e9 100644 (file)
     <ProjectReference Include="$(LibrariesProjectRoot)System.Diagnostics.DiagnosticSource\src\System.Diagnostics.DiagnosticSource.csproj" /> 
   </ItemGroup>
 
-  <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
-    <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
-  </ItemGroup>
-
   <ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Bcl.AsyncInterfaces\src\Microsoft.Bcl.AsyncInterfaces.csproj" />
   </ItemGroup>