autofac/Autofac

OpenGenericServiceBinder.TryBindOpenGenericTypedService behavior does not match assignability rules for uncommon edgecase

已關閉

#1,464 建立於 2025年8月4日

 (2 則留言) (0 個反應) (0 位負責人)C# (827 個分叉)batch import
bughelp wanted

倉庫指標

星標
 (4,327 顆星)
PR 合併指標
 (平均合併 3小時 24分鐘) (30 天內合併 10 個 PR)

描述

Describe the Bug

Upon further debugging, OpenGenericServiceBinder.TryBindOpenGenericTypedService returns false where Type.IsAssignableFrom returns true for edgecase where generic parameters are not passed through to interfaces:

For example:

class InsaneUsecase<TParam> : IWhatever<int> {}

Is assignable to IWhatever<int> but Autofac consider this not a match.

When interfaces deemed by Autofac to be incompatible are inherited (thus placed first on list, which can be simulated manually as described below), it causes weird issues possibly relating to https://github.com/autofac/Autofac/issues/1465 but the core issue is independent.


Nested open generics cause order of interfaces to matter when resolving enumeration.

Issue can creep out through base classes, as inherited interfaces are earlier when enumerating interfaces.

There's probably some silly oversight when checking interfaces of Type somewhere in depths of Autofac.

Steps to Reproduce

using Autofac;

namespace xunit_tests
{
    public class UnitTest1
    {
        [Fact]
        public void WeirdBug()
        {
            ContainerBuilder builder = new ContainerBuilder();

            builder
                .RegisterGeneric(typeof(Handler_x_then_base<>))
                .As(typeof(IHandler<>).MakeGenericType(typeof(IRequest<>)));
            builder
                .RegisterGeneric(typeof(Handler_base_then_x<>))
                .As(typeof(IHandler<>).MakeGenericType(typeof(IRequest<>)));

            var container = builder.Build();

            var handlers = container
                .Resolve<IEnumerable<IHandler<IRequest<int>>>>();

            // both handlers should be resolved, but only one is
            Assert.Equal(2, handlers.Count());
        }

        public interface IHandler<in T> { }
        public interface IRequest { }
        public interface IRequest<T> { }

        // working 
        public class Handler_x_then_base<TParam>
        : IHandler<IRequest<TParam>>, IHandler<IRequest>
        { }
        // not working, same as if IHandler<IRequest> were to be inherited via base class
        public class Handler_base_then_x<TParam>
        : IHandler<IRequest>, IHandler<IRequest<TParam>>
        { }

        // overcomplicated usecase that works because there is one less generic level
        [Fact]
        public void SimplerCaseWorks()
        {
            ContainerBuilder builder = new ContainerBuilder();

            builder
                .RegisterGeneric(typeof(Thing<>))
                .As(typeof(IThing<>));
            builder
                .RegisterGeneric(typeof(Stuff<,>))
                .As(typeof(IThing<>.IStuff<>));
            builder
                .RegisterGeneric(typeof(ThingStuffA<,>))
                .As(typeof(IThing<>.IStuff<>));
            builder
                .RegisterGeneric(typeof(ThingStuffB<,>))
                .As(typeof(IThing<>.IStuff<>));
            builder
                .RegisterGeneric(typeof(ThingStuffC<,>))
                .As(typeof(IThing<>.IStuff<>));

            var container = builder.Build();

            var things = container.Resolve<IEnumerable<IThing<int>>>();
            var stuff = container.Resolve<IEnumerable<IThing<int>.IStuff<string>>>();

            Assert.Equal(1, things.Count());
            Assert.Equal(4, stuff.Count());
        }

        public interface IThing<TThing>
        {
            public interface IStuff<TStuff> { }
        }

        public class Thing<T> : IThing<T> { }
        public class Stuff<TThing, TStuff> : IThing<TThing>.IStuff<TStuff> { }
        public class ThingStuffA<TThing, TStuff> : IThing<TThing>, IThing<TThing>.IStuff<TStuff> { }
        public class ThingStuffB<TThing, TStuff> : IThing<TThing>.IStuff<TStuff>, IThing<TThing> { }
        public class ThingStuffC<TThing, TStuff> : Thing<TThing>, IThing<TThing>.IStuff<TStuff> { }
    }
}

Expected Behavior

Order of interfaces should not matter

Exception with Stack Trace

No exception

Dependency Versions

Autofac: 8.3.0 Issue was found on 7.1.0 in .NET 4.8, reproduced in 8.3.0 in .NET 4.8 and in .NET 8

貢獻者指南