Skip to content

Commit

Permalink
Porting changes from dotnet/extensions#536
Browse files Browse the repository at this point in the history
  • Loading branch information
jbogard committed Apr 1, 2020
1 parent ab45b51 commit 05b0be1
Show file tree
Hide file tree
Showing 16 changed files with 634 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private ServiceCallSite TryCreateOpenGeneric(Type serviceType, CallSiteChain cal
if (serviceType.IsConstructedGenericType
&& _descriptorLookup.TryGetValue(serviceType.GetGenericTypeDefinition(), out var descriptor))
{
return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot);
return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot, true);
}

return null;
Expand Down Expand Up @@ -165,7 +165,7 @@ private ServiceCallSite TryCreateEnumerable(Type serviceType, CallSiteChain call
{
var descriptor = _descriptors[i];
var callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot) ??
TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot);
TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot, false);

if (callSite != null)
{
Expand Down Expand Up @@ -231,14 +231,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);
var closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
Type closedType;
try
{
closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
}
catch (ArgumentException ex)
{
if (throwOnConstraintViolation)
{
throw new InvalidOperationException(Resources.FormatGenericConstraintViolation(serviceType, descriptor.ImplementationType), ex);
}

return null;
}

return CreateConstructorCallSite(lifetime, serviceType, closedType, callSiteChain);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,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()
{
// 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
@@ -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 abstract class AbstractClass
{

}
}
Original file line number Diff line number Diff line change
@@ -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<ClassImplementingIComparable>
{
public int CompareTo(ClassImplementingIComparable other) => 0;
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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
{

}
}
Original file line number Diff line number Diff line change
@@ -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<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,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<T> : IFakeOpenGenericService<T>
where T : class
{
public T Value { get; } = default;
}
}
Original file line number Diff line number Diff line change
@@ -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<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,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<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,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<T> : IFakeOpenGenericService<T>
{
public T Value { get; } = default;
}
}
Original file line number Diff line number Diff line change
@@ -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<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,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<T> : IFakeOpenGenericService<T>
where T : struct
{
public T Value { get; } = default;
}
}
Original file line number Diff line number Diff line change
@@ -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<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 @@ -4,7 +4,7 @@

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

0 comments on commit 05b0be1

Please sign in to comment.