Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Support constrained open generic types #471

Closed
jbogard opened this issue Dec 29, 2016 · 9 comments
Closed

Support constrained open generic types #471

jbogard opened this issue Dec 29, 2016 · 9 comments

Comments

@jbogard
Copy link

jbogard commented Dec 29, 2016

If I register a collection of open generics, some of the open generics may be constrained. Consider the interface and implementations:

    public interface IFakeOpenGenericService<TValue>
    {
        TValue Value { get; }
    }
    public class FakeOpenGenericService<TVal> : IFakeOpenGenericService<TVal>
    {
        public FakeOpenGenericService(TVal value)
        {
            Value = value;
        }

        public TVal Value { get; }
    }
    public class ConstrainedFakeOpenGenericService<TVal> : IFakeOpenGenericService<TVal> 
        where TVal : PocoClass
    {
        public ConstrainedFakeOpenGenericService(TVal value)
        {
            Value = value;
        }

        public TVal Value { get; }
    }

If I register multiple services:

collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>));
collection.AddTransient(typeof(IFakeOpenGenericService<>), typeof(ConstrainedFakeOpenGenericService<>));

And try to resolve, the version that adheres to all constraints works, but if I try to get a collection with a generic parameter that doesn't match the constraint, I get an exception:

var allServices = provider.GetServices<IFakeOpenGenericService<PocoClass>>().ToList();
var constrainedServices = provider.GetServices<IFakeOpenGenericService<IFakeSingletonService>>().ToList();            

Exception:

      System.ArgumentException : GenericArguments[0], 'Microsoft.Extensions.DependencyInjection.Specification.Fakes.IFakeSingletonService', on 'Microsoft.Extensions.DependencyInjection.Specification.Fakes.ConstrainedFakeOpenGenericService`1[TVal]' violates the constraint of type 'TVal'.
      ---- System.TypeLoadException : GenericArguments[0], 'Microsoft.Extensions.DependencyInjection.Specification.Fakes.IFakeSingletonService', on 'Microsoft.Extensions.DependencyInjection.Specification.Fakes.ConstrainedFakeOpenGenericService`1[TVal]' violates the constraint of type parameter 'TVal'.
      Stack Trace:
           at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
           at System.RuntimeType.MakeGenericType(Type[] instantiation)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\ServiceLookup\GenericService.cs(26,0): at Microsoft.Extensions.DependencyInjection.ServiceLookup.GenericService.GetService(Type closedServiceType)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\ServiceLookup\ServiceTable.cs(102,0): at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceTable.TryGetEntry(Type serviceType, ServiceEntry& entry)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\ServiceLookup\OpenIEnumerableService.cs(28,0): at Microsoft.Extensions.DependencyInjection.ServiceLookup.OpenIEnumerableService.GetService(Type closedServiceType)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\ServiceLookup\ServiceTable.cs(102,0): at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceTable.TryGetEntry(Type serviceType, ServiceEntry& entry)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\ServiceProvider.cs(114,0): at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetServiceCallSite(Type serviceType, ISet`1 callSiteChain)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\ServiceProvider.cs(73,0): at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType, ServiceProvider serviceProvider)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\Internal\ConcurrentDictionaryExtensions.cs(24,0): at System.Collections.Concurrent.ConcurrentDictionaryExtensions.GetOrAdd[TKey,TValue,TArg](ConcurrentDictionary`2 dictionary, TKey key, Func`3 valueFactory, TArg arg)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection\ServiceProvider.cs(64,0): at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection.Abstractions\ServiceProviderServiceExtensions.cs(56,0): at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection.Abstractions\ServiceProviderServiceExtensions.cs(79,0): at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection.Abstractions\ServiceProviderServiceExtensions.cs(95,0): at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetServices[T](IServiceProvider provider)
        C:\dev\aspnet-DependencyInjection\src\Microsoft.Extensions.DependencyInjection.Specification.Tests\DependencyInjectionSpecificationTests.cs(561,0): at Microsoft.Extensions.DependencyInjection.Specification.DependencyInjectionSpecificationTests.ConstrainedOpenGenericServicesCanBeResolved()
        ----- Inner Stack Trace -----
           at System.RuntimeTypeHandle.Instantiate(RuntimeTypeHandle handle, IntPtr* pInst, Int32 numGenericArgs, ObjectHandleOnStack type)
           at System.RuntimeTypeHandle.Instantiate(Type[] inst)
           at System.RuntimeType.MakeGenericType(Type[] instantiation)

Expected: constrained open generics are tested before attempted to instantiate. It looks like the only way to reliably test is to try-catch-swallow, see http://stackoverflow.com/a/4864565/58508

PR with failing test coming shortly.

@davidfowl
Copy link
Member

Expected: constrained open generics are tested before attempted to instantiate. It looks like the only way to reliably test is to try-catch-swallow, see http://stackoverflow.com/a/4864565/58508

That's pretty unfortunate.

What's the scenarios for this?

@jbogard
Copy link
Author

jbogard commented Dec 29, 2016

I'm adding a collection of filters for my own in-memory pipes-and-filters implementation in MediatR. My real interface looks like:

public delegate Task<TResponse> RequestHandlerDelegate<TResponse>();

public interface IPipelineBehavior<TRequest, TResponse>
{
    Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next);
}

These behaviors act like filters around a core IHandler<TRequest, TResponse> implementation. Then a common scenario is to have some sort of request pre-processor, say to supplement a request with additional information or to mutate the information. One example we had was inputs coming from an optical scanner might need leading zeroes trimmed/added. We could register a pipeline behavior:

public class TrimOrderNumberLeadingZeros<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IContainOrderNumber
{
    public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    {
        request.OrderNumber = message.OrderNumber.TrimLeadingZeros();
        return next();
    }
}

If I made the behavior closed instead of constrained, it conflicts with rules around "favor closed over open" (which is consistent with at least StructureMap). So if I keep it an open generic type, but merely constrain it, then I can get the correct IEnumerable<TService> based on which implementations of TService can successfully close the open generic type.

I assume some F# person is going to read all this and laugh one day.

@jbogard
Copy link
Author

jbogard commented Dec 30, 2016

@Eilon
Copy link
Member

Eilon commented Jan 6, 2017

We are not planning to include this feature here, in part because it doesn't seem to be broadly available in other DI systems and demand doesn't seem very high. We're also concerned about the first chance exception that gets thrown.

@t03apt
Copy link

t03apt commented Jan 19, 2018

@Eilon Is there any way to override some defaults so that we could get the expected behavior mentioned by @jbogard?

Expected: constrained open generics are tested before attempted to instantiate.

NOTE: Same use can be solved using many other DI containers such as Autofac, DryIoC, LightInject, StructureMap, Unity and Castle Windsor. See: MediatR Container-Feature-Support, Test source code

@Eilon
Copy link
Member

Eilon commented Jan 19, 2018

@t03apt sorry I'm not sure if that's possible.

@mmillican
Copy link

I'm trying to implement something similar with constrained generics. Would be super helpful to have this built into the DI system.

@jbogard have you had any luck with a work around for this?

@jbogard
Copy link
Author

jbogard commented Jan 31, 2018

No, I had to pick one of the other containers. We were trying to get away with just using the built-in container but it's just not possible today.

@jbogard
Copy link
Author

jbogard commented Apr 3, 2018

In case anyone is wondering, the following containers support this feature:

  • Autofac
  • DryIoc
  • LightInject
  • Simple Injector
  • StructureMap
  • Lamar
  • Castle Windsor

The following do not:

  • This one
  • Ninject
  • Unity

The latter two I thought were dead but seem to be undergoing a revival of sorts.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants