diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx
index 354fe5fccbfa5a..134d98fdd448c8 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx
@@ -159,4 +159,7 @@
'{0}' type only implements IAsyncDisposable. Use DisposeAsync to dispose the container.
+
+ Generic constraints violated for type '{0}' while attempting to activate '{1}'.
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs
index 2bf5ee16fc8e15..9adc47de00d87d 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs
@@ -118,7 +118,7 @@ private ServiceCallSite TryCreateOpenGeneric(Type serviceType, CallSiteChain cal
if (serviceType.IsConstructedGenericType
&& _descriptorLookup.TryGetValue(serviceType.GetGenericTypeDefinition(), out ServiceDescriptorCacheItem descriptor))
{
- return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot);
+ return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot, true);
}
return null;
@@ -164,7 +164,7 @@ private ServiceCallSite TryCreateEnumerable(Type serviceType, CallSiteChain call
{
ServiceDescriptor descriptor = _descriptors[i];
ServiceCallSite callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot) ??
- TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot);
+ TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot, false);
if (callSite != null)
{
@@ -230,14 +230,28 @@ private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type servic
return null;
}
- private ServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
+ private ServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
{
if (serviceType.IsConstructedGenericType &&
serviceType.GetGenericTypeDefinition() == descriptor.ServiceType)
{
Debug.Assert(descriptor.ImplementationType != null, "descriptor.ImplementationType != null");
var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
- Type closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
+ Type closedType;
+ try
+ {
+ closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
+ }
+ catch (ArgumentException ex)
+ {
+ if (throwOnConstraintViolation)
+ {
+ throw new InvalidOperationException(SR.Format(SR.GenericConstraintViolation, serviceType, descriptor.ImplementationType), ex);
+ }
+
+ return null;
+ }
+
return CreateConstructorCallSite(lifetime, serviceType, closedType, callSiteChain);
}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs
index 15295982782f12..ccb31e1105b192 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/Autofac.cs
@@ -7,9 +7,14 @@
namespace Microsoft.Extensions.DependencyInjection.Specification
{
- public class AutofacDependencyInjectionSpecificationTests: DependencyInjectionSpecificationTests
+ public class AutofacDependencyInjectionSpecificationTests : SkippableDependencyInjectionSpecificationTests
{
- protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
+ public override string[] SkippedTests => new[]
+ {
+ "PublicNoArgCtorConstrainedOpenGenericServicesCanBeResolved"
+ };
+
+ protected override IServiceProvider CreateServiceProviderImpl(IServiceCollection serviceCollection)
{
var builder = new ContainerBuilder();
builder.Populate(serviceCollection);
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/StashBox.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/StashBox.cs
index 28a27f028af5a4..62986632a18abd 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/StashBox.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.External.Tests/StashBox.cs
@@ -5,9 +5,16 @@
namespace Microsoft.Extensions.DependencyInjection.Specification
{
- public class StashBoxDependencyInjectionSpecificationTests: DependencyInjectionSpecificationTests
+ public class StashBoxDependencyInjectionSpecificationTests : SkippableDependencyInjectionSpecificationTests
{
- protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
+ public override string[] SkippedTests => new[]
+ {
+ "PublicNoArgCtorConstrainedOpenGenericServicesCanBeResolved",
+ "SelfReferencingConstrainedOpenGenericServicesCanBeResolved",
+ "ClassConstrainedOpenGenericServicesCanBeResolved"
+ };
+
+ protected override IServiceProvider CreateServiceProviderImpl(IServiceCollection serviceCollection)
{
return serviceCollection.UseStashbox();
}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/DependencyInjectionSpecificationTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/DependencyInjectionSpecificationTests.cs
index e50d9ca8b649fa..9d82878dd3575b 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/DependencyInjectionSpecificationTests.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/DependencyInjectionSpecificationTests.cs
@@ -591,6 +591,160 @@ public void OpenGenericServicesCanBeResolved()
Assert.Same(singletonService, genericService.Value);
}
+ [Fact]
+ public void ConstrainedOpenGenericServicesCanBeResolved()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ConstrainedFakeOpenGenericService<>));
+ var poco = new PocoClass();
+ collection.AddSingleton(poco);
+ collection.AddSingleton();
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var allServices = provider.GetServices>().ToList();
+ var constrainedServices = provider.GetServices>().ToList();
+ var singletonService = provider.GetService();
+ // Assert
+ Assert.Equal(2, allServices.Count);
+ Assert.Same(poco, allServices[0].Value);
+ Assert.Same(poco, allServices[1].Value);
+ Assert.Equal(1, constrainedServices.Count);
+ Assert.Same(singletonService, constrainedServices[0].Value);
+ }
+
+ [Fact]
+ public void ConstrainedOpenGenericServicesReturnsEmptyWithNoMatches()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ConstrainedFakeOpenGenericService<>));
+ collection.AddSingleton();
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var constrainedServices = provider.GetServices>().ToList();
+ // Assert
+ Assert.Equal(0, constrainedServices.Count);
+ }
+
+ [Fact]
+ public void InterfaceConstrainedOpenGenericServicesCanBeResolved()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithInterfaceConstraint<>));
+ var enumerableVal = new ClassImplementingIEnumerable();
+ collection.AddSingleton(enumerableVal);
+ collection.AddSingleton();
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var allServices = provider.GetServices>().ToList();
+ var constrainedServices = provider.GetServices>().ToList();
+ var singletonService = provider.GetService();
+ // Assert
+ Assert.Equal(2, allServices.Count);
+ Assert.Same(enumerableVal, allServices[0].Value);
+ Assert.Same(enumerableVal, allServices[1].Value);
+ Assert.Equal(1, constrainedServices.Count);
+ Assert.Same(singletonService, constrainedServices[0].Value);
+ }
+
+ [Fact]
+ public void PublicNoArgCtorConstrainedOpenGenericServicesCanBeResolved()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>));
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNewConstraint<>));
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var allServices = provider.GetServices>().ToList();
+ var constrainedServices = provider.GetServices>().ToList();
+ // Assert
+ Assert.Equal(2, allServices.Count);
+ Assert.Equal(1, constrainedServices.Count);
+ }
+
+ [Fact]
+ public void ClassConstrainedOpenGenericServicesCanBeResolved()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>));
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithClassConstraint<>));
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var allServices = provider.GetServices>().ToList();
+ var constrainedServices = provider.GetServices>().ToList();
+ // Assert
+ Assert.Equal(2, allServices.Count);
+ Assert.Equal(1, constrainedServices.Count);
+ }
+
+ [Fact]
+ public void StructConstrainedOpenGenericServicesCanBeResolved()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>));
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithStructConstraint<>));
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var allServices = provider.GetServices>().ToList();
+ var constrainedServices = provider.GetServices>().ToList();
+ // Assert
+ Assert.Equal(2, allServices.Count);
+ Assert.Equal(1, constrainedServices.Count);
+ }
+
+ [Fact]
+ public void AbstractClassConstrainedOpenGenericServicesCanBeResolved()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithAbstractClassConstraint<>));
+ var poco = new PocoClass();
+ collection.AddSingleton(poco);
+ var classInheritingClassInheritingAbstractClass = new ClassInheritingClassInheritingAbstractClass();
+ collection.AddSingleton(classInheritingClassInheritingAbstractClass);
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var allServices = provider.GetServices>().ToList();
+ var constrainedServices = provider.GetServices>().ToList();
+ // Assert
+ Assert.Equal(2, allServices.Count);
+ Assert.Same(classInheritingClassInheritingAbstractClass, allServices[0].Value);
+ Assert.Same(classInheritingClassInheritingAbstractClass, allServices[1].Value);
+ Assert.Equal(1, constrainedServices.Count);
+ Assert.Same(poco, constrainedServices[0].Value);
+ }
+
+ [Fact]
+ public void SelfReferencingConstrainedOpenGenericServicesCanBeResolved()
+ {
+ // Arrange
+ var collection = new TestServiceCollection();
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
+ collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithSelfReferencingConstraint<>));
+ var poco = new PocoClass();
+ collection.AddSingleton(poco);
+ var selfComparable = new ClassImplementingIComparable();
+ collection.AddSingleton(selfComparable);
+ var provider = CreateServiceProvider(collection);
+ // Act
+ var allServices = provider.GetServices>().ToList();
+ var constrainedServices = provider.GetServices>().ToList();
+ // Assert
+ Assert.Equal(2, allServices.Count);
+ Assert.Same(selfComparable, allServices[0].Value);
+ Assert.Same(selfComparable, allServices[1].Value);
+ Assert.Equal(1, constrainedServices.Count);
+ Assert.Same(poco, constrainedServices[0].Value);
+ }
+
[Fact]
public void ClosedServicesPreferredOverOpenGenericServices()
{
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/AbstractClass.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/AbstractClass.cs
new file mode 100644
index 00000000000000..412a042b35d842
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/AbstractClass.cs
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public abstract class AbstractClass
+ {
+
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassImplementingIComparable.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassImplementingIComparable.cs
new file mode 100644
index 00000000000000..02ffe2e9c7961b
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassImplementingIComparable.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassImplementingIComparable : IComparable
+ {
+ public int CompareTo(ClassImplementingIComparable other) => 0;
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassImplementingIEnumerable.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassImplementingIEnumerable.cs
new file mode 100644
index 00000000000000..20fe4e2fc7ce63
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassImplementingIEnumerable.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassImplementingIEnumerable : IEnumerable
+ {
+ public IEnumerator GetEnumerator() => throw new NotImplementedException();
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassInheritingAbstractClass.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassInheritingAbstractClass.cs
new file mode 100644
index 00000000000000..ec37f2b103a2c5
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassInheritingAbstractClass.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassInheritingAbstractClass : AbstractClass
+ {
+
+ }
+
+ public class ClassAlsoInheritingAbstractClass : AbstractClass
+ {
+
+ }
+
+ public class ClassInheritingClassInheritingAbstractClass : ClassInheritingAbstractClass
+ {
+
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithAbstractClassConstraint.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithAbstractClassConstraint.cs
new file mode 100644
index 00000000000000..e5519863348d66
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithAbstractClassConstraint.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassWithAbstractClassConstraint : IFakeOpenGenericService
+ where T : AbstractClass
+ {
+ public ClassWithAbstractClassConstraint(T value) => Value = value;
+
+ public T Value { get; }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithClassConstraint.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithClassConstraint.cs
new file mode 100644
index 00000000000000..b180203a688a71
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithClassConstraint.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassWithClassConstraint : IFakeOpenGenericService
+ where T : class
+ {
+ public T Value { get; } = default;
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithInterfaceConstraint.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithInterfaceConstraint.cs
new file mode 100644
index 00000000000000..efd2c9c6cef63d
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithInterfaceConstraint.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassWithInterfaceConstraint : IFakeOpenGenericService
+ where T : IEnumerable
+ {
+ public ClassWithInterfaceConstraint(T value) => Value = value;
+
+ public T Value { get; }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithNewConstraint.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithNewConstraint.cs
new file mode 100644
index 00000000000000..143986cfa11200
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithNewConstraint.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassWithNewConstraint : IFakeOpenGenericService
+ where T : new()
+ {
+ public T Value { get; } = new T();
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithNoConstraint.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithNoConstraint.cs
new file mode 100644
index 00000000000000..848e7551cd6a30
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithNoConstraint.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassWithNoConstraints : IFakeOpenGenericService
+ {
+ public T Value { get; } = default;
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithSelfReferencingConstraint.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithSelfReferencingConstraint.cs
new file mode 100644
index 00000000000000..0e464290229407
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithSelfReferencingConstraint.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassWithSelfReferencingConstraint : IFakeOpenGenericService
+ where T : IComparable
+ {
+ public ClassWithSelfReferencingConstraint(T value) => Value = value;
+
+ public T Value { get; }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithStructConstraint.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithStructConstraint.cs
new file mode 100644
index 00000000000000..06355eb905ffed
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ClassWithStructConstraint.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ClassWithStructConstraint : IFakeOpenGenericService
+ where T : struct
+ {
+ public T Value { get; } = default;
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ConstrainedFakeOpenGenericService.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ConstrainedFakeOpenGenericService.cs
new file mode 100644
index 00000000000000..940e3621a8772b
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/ConstrainedFakeOpenGenericService.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
+{
+ public class ConstrainedFakeOpenGenericService : IFakeOpenGenericService
+ where TVal : PocoClass
+ {
+ public ConstrainedFakeOpenGenericService(TVal value)
+ {
+ Value = value;
+ }
+ public TVal Value { get; }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/IFakeOpenGenericService.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/IFakeOpenGenericService.cs
index 71870357fd9d31..d556939012de31 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/IFakeOpenGenericService.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Specification.Tests/Fakes/IFakeOpenGenericService.cs
@@ -3,7 +3,7 @@
namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
- public interface IFakeOpenGenericService
+ public interface IFakeOpenGenericService
{
TValue Value { get; }
}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/Fakes/AbstractClass.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/Fakes/AbstractClass.cs
deleted file mode 100644
index b2d1cff2734b85..00000000000000
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/Fakes/AbstractClass.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.Extensions.DependencyInjection.Tests.Fakes
-{
- public abstract class AbstractClass
- {
- public AbstractClass()
- {
- }
- }
-}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
index bc0988f06d64d1..b8d8e6a0fcd784 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
@@ -111,6 +111,299 @@ public void CreateCallSite_UsesNullaryConstructorIfServicesCannotBeInjectedIntoO
Assert.Empty(ctorCallSite.ParameterCallSites);
}
+ [Fact]
+ public void CreateCallSite_Throws_IfClosedTypeDoesNotSatisfyStructGenericConstraint()
+ {
+ // Arrange
+ var serviceType = typeof(IFakeOpenGenericService<>);
+ var implementationType = typeof(ClassWithStructConstraint<>);
+ var descriptor = new ServiceDescriptor(serviceType, implementationType, ServiceLifetime.Transient);
+ var callSiteFactory = GetCallSiteFactory(descriptor);
+ // Act
+ var nonMatchingType = typeof(IFakeOpenGenericService