Scrutor icon indicating copy to clipboard operation
Scrutor copied to clipboard

ReflectionExtensions.HasMatchingGenericArity has a bug

Open ocdogan opened this issue 5 years ago • 1 comments

When the implementation class is a generic class that is implementing a generic interface as below, it's not able to detect IEventHandler<CustomEvent> and IEventHandler from ActionableEventHandler<CustomEvent>

class CustomEvent { } 
interface IEventHandler { }
interface IEventHandler<TEvent> : IEventHandler { }
class EventHandler<TEvent> : IEventHandler<TEvent> { }
class ActionableEventHandler<TEvent> : EventHandler<TEvent> { }

Possible fix can be:

public static bool HasMatchingGenericArity(this Type interfaceType, TypeInfo typeInfo) {
            if (typeInfo.IsGenericType) {
                var interfaceTypeInfo = interfaceType.GetTypeInfo();
                if (interfaceTypeInfo.IsGenericType) {
                    var interfaceArgCount = interfaceType.GenericTypeArguments.Length;
                    var implementationArgCount = typeInfo.GenericTypeArguments.Length;

                    return interfaceArgCount == implementationArgCount;
                }
                // return false;
            }
            return true;
        }

ocdogan avatar Oct 22 '19 14:10 ocdogan

I am seeing the same issue. Basically if the concrete class has fewer generic parameters than the interfaces, the HasMatchingGenericArity predicate fails and the interface is not added. This check is correct to do when trying to register open generics (and maybe partially closed generics?) but not fully closed generics.

var servicesA = new ServiceCollection();
servicesA.Scan(scan => scan.AddTypes(typeof(Service<int>))
                          .AsImplementedInterfaces()
                          .WithTransientLifetime());

// Will throw since nothing was added.
//var descriptorA = servicesA[0];
//Console.WriteLine($"ServiceType: {descriptorA.ServiceType}, ImplType: {descriptorA.ImplementationType}");

var servicesB = new ServiceCollection();
servicesB.Scan(scan => scan.AddTypes(typeof(Service<int>))
                          .As(static type => type.GetInterfaces())       // Works without the HasMatchingGenericArity check
                          .WithTransientLifetime());

var descriptorB = servicesB[0];
Console.WriteLine($"ServiceType: {descriptorB.ServiceType}, ImplType: {descriptorB.ImplementationType}");

public interface IServiceA<T1,T2>{}
public class Service<T> : IServiceA<string,T> {}

Fiddle

leorg99 avatar Jul 12 '22 20:07 leorg99