Skip to content
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3431716
Pulling over everything from https://github.com/aspnet/DependencyInje…
jbogard Nov 18, 2018
1286891
Removing LINQ from hot path; Cosmetic fixes
jbogard Nov 19, 2018
3ba35c9
Removing more LINQ in favor of explicit checks
jbogard Nov 26, 2018
2d241e2
Adding tests for positive cases
jbogard Nov 26, 2018
d7a3b82
Adding tests for abstract class constraint
jbogard Nov 27, 2018
bb1d6a8
Adding complex test for self-referencing constraint
jbogard Nov 27, 2018
f32e5b8
Removing LINQ in favor of explicit code
jbogard Nov 27, 2018
64e6808
Switching operator
jbogard Nov 27, 2018
a0b1e93
Inverting conditional as part of conditional operator
jbogard Nov 27, 2018
88b6c1d
Moving fakes over to specification
jbogard Nov 27, 2018
70b4847
Adding spec tests for more complex scenarios; combining ifs
jbogard Nov 27, 2018
7246fd4
Removing ctors from constraints; adding full benchmarks; Adding spec …
jbogard Nov 28, 2018
584de56
Adding spec test for resolving single non-matching generically constr…
jbogard Nov 28, 2018
8cde246
Adjusting benchmarks to target CallSiteFactory
jbogard Nov 29, 2018
9f866e0
Setting baseline
jbogard Nov 29, 2018
7499ba2
Fixing derived class and adjusting benchmarks to use consistent services
jbogard Nov 29, 2018
0626cdc
Throwing on singular open generic constraint violation; adding except…
jbogard Nov 29, 2018
f99b9da
Testing exception messages
jbogard Nov 29, 2018
ae792ae
Using simpler try-catch flow to test for closing generics
jbogard Dec 12, 2018
35c1f89
Merge branch 'master' into ConstrainedOpenGenerics
jbogard Dec 13, 2018
6e73daa
Adding explicit classes to not rely on primitives
jbogard Dec 14, 2018
66a64ba
Skipping failing spec tests for Autofac and StashBox
jbogard Dec 14, 2018
cd1b606
Merge branch 'master' into ConstrainedOpenGenerics
jbogard Feb 26, 2019
e0553cb
Fixing tests
jbogard Feb 26, 2019
90bfb82
Merge branch 'master' into ConstrainedOpenGenerics
jbogard Nov 15, 2019
8fae420
Merge branch 'master' into ConstrainedOpenGenerics
jbogard Mar 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/DependencyInjection/DI.External.Tests/test/Autofac.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 9 additions & 2 deletions src/DependencyInjection/DI.External.Tests/test/StashBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link

Choose a reason for hiding this comment

The 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:

  • Register an open generic with no constraints. This should work for any T in the generic.
  • Register an open generic with a constraint of new(). This will only work for things with a public constructor.
  • Resolve all the services using a class that can be constructed. You should get two.
  • Resolve all the services using a class that doesn't satisfy the constraint. The constraint violation will be ignored but the others will be resolved.

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...

  • In the single instance, you'd get an exception. The last thing you registered - the override - doesn't satisfy the constraint, so resolution fails.
  • In the collection, you get a sort of "filtered" collection. Only things that satisfy the constraint are resolved.

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?

Copy link
Author

Choose a reason for hiding this comment

The 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()
{
Expand Down
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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
{
public interface IFakeOpenGenericService<TValue>
public interface IFakeOpenGenericService<out TValue>
{
TValue Value { get; }
}
Expand Down
Loading