-
Notifications
You must be signed in to change notification settings - Fork 851
Adding support for constrained open generics #536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 24 commits
3431716
1286891
3ba35c9
2d241e2
d7a3b82
bb1d6a8
f32e5b8
64e6808
a0b1e93
88b6c1d
70b4847
7246fd4
584de56
8cde246
9f866e0
7499ba2
0626cdc
f99b9da
ae792ae
35c1f89
6e73daa
66a64ba
cd1b606
e0553cb
90bfb82
8fae420
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -545,6 +545,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<IFakeSingletonService, FakeService>(); | ||
| var provider = CreateServiceProvider(collection); | ||
| // Act | ||
| var allServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList(); | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<IFakeSingletonService>>().ToList(); | ||
| var singletonService = provider.GetService<IFakeSingletonService>(); | ||
| // 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<IFakeSingletonService, FakeService>(); | ||
| var provider = CreateServiceProvider(collection); | ||
| // Act | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<IFakeSingletonService>>().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<IFakeSingletonService, FakeService>(); | ||
| var provider = CreateServiceProvider(collection); | ||
| // Act | ||
| var allServices = provider.GetServices<IFakeOpenGenericService<ClassImplementingIEnumerable>>().ToList(); | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<IFakeSingletonService>>().ToList(); | ||
| var singletonService = provider.GetService<IFakeSingletonService>(); | ||
| // 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() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this test has an invalid premise, so I'm hoping to get clarification on the intent. I think it may have an impact on how the spec for constraints should work in a single instance case. Here's how I'm reading it:
A challenge I'm having is that this impacts the way a single instance resolution would work. var collection = new TestServiceCollection();
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ClassWithNewConstraint<>));
var provider = CreateServiceProvider(collection);
// What do you get here? Exception? Fallback to ClassWithNoConstraint<T>? null?
var singleService = provider.GetService<IFakeOpenGenericService<ClassWithPrivateCtor>>();From autofac/Autofac.Extensions.DependencyInjection#40 I gather the intent is...
From a use case perspective, let's say I have a bunch of message handlers. I register them all and resolve the collection of them. Sometimes I get them all and sometimes I don't. How do I know why? How do I troubleshoot that? I'm not sure I agree with the filtered collection concept. It may pass on the base container, but I'm not sure it should. I'd expect any constraint violation to notify me. Perhaps I'm alone in that?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't think of it as "resolving a collection". I'm not registering a static collection. I'm registering a bunch of individual handlers, then I come back later and ask "find all the handlers that match this specification". |
||
| { | ||
| // 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<IFakeOpenGenericService<PocoClass>>().ToList(); | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<ClassWithPrivateCtor>>().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<IFakeOpenGenericService<PocoClass>>().ToList(); | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<int>>().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<IFakeOpenGenericService<int>>().ToList(); | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().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<IFakeOpenGenericService<ClassInheritingClassInheritingAbstractClass>>().ToList(); | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().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<IFakeOpenGenericService<ClassImplementingIComparable>>().ToList(); | ||
| var constrainedServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().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() | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,9 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Tests.Fakes | ||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public abstract class AbstractClass | ||
| { | ||
| public AbstractClass() | ||
| { | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using System; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassImplementingIComparable : IComparable<ClassImplementingIComparable> | ||
| { | ||
| public int CompareTo(ClassImplementingIComparable other) => 0; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| using System; | ||
| using System.Collections; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassImplementingIEnumerable : IEnumerable | ||
| { | ||
| public IEnumerator GetEnumerator() => throw new NotImplementedException(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassInheritingAbstractClass : AbstractClass | ||
| { | ||
|
|
||
| } | ||
|
|
||
| public class ClassAlsoInheritingAbstractClass : AbstractClass | ||
| { | ||
|
|
||
| } | ||
|
|
||
| public class ClassInheritingClassInheritingAbstractClass : ClassInheritingAbstractClass | ||
| { | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassWithAbstractClassConstraint<T> : IFakeOpenGenericService<T> | ||
| where T : AbstractClass | ||
| { | ||
| public ClassWithAbstractClassConstraint(T value) => Value = value; | ||
|
|
||
| public T Value { get; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassWithClassConstraint<T> : IFakeOpenGenericService<T> | ||
| where T : class | ||
| { | ||
| public T Value { get; } = default; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
| using System.Collections; | ||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassWithInterfaceConstraint<T> : IFakeOpenGenericService<T> | ||
| where T : IEnumerable | ||
| { | ||
| public ClassWithInterfaceConstraint(T value) => Value = value; | ||
|
|
||
| public T Value { get; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassWithNewConstraint<T> : IFakeOpenGenericService<T> | ||
| where T : new() | ||
| { | ||
| public T Value { get; } = new T(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassWithNoConstraints<T> : IFakeOpenGenericService<T> | ||
| { | ||
| public T Value { get; } = default; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassWithSelfReferencingConstraint<T> : IFakeOpenGenericService<T> | ||
| where T : IComparable<T> | ||
| { | ||
| public ClassWithSelfReferencingConstraint(T value) => Value = value; | ||
|
|
||
| public T Value { get; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ClassWithStructConstraint<T> : IFakeOpenGenericService<T> | ||
| where T : struct | ||
| { | ||
| public T Value { get; } = default; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
| namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes | ||
| { | ||
| public class ConstrainedFakeOpenGenericService<TVal> : IFakeOpenGenericService<TVal> | ||
| where TVal : PocoClass | ||
| { | ||
| public ConstrainedFakeOpenGenericService(TVal value) | ||
| { | ||
| Value = value; | ||
| } | ||
| public TVal Value { get; } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.