Using `GetMethod` on class implementing an interface with a default method implementation returns null

927 views Asked by At

I have a couple interfaces (IMapFrom and IMapTo) that allow me to simplify my AutoMapper configurations. Each of the interfaces has a default implementation for the MapTo and MapFrom methods. I have a separate MappingProfile class that uses reflection to find all of the implementing classes, and invokes their map creation.

The aforementioned classes look like so:

public interface IMapFrom<T>
{
    void MapFrom(Profile profile) => profile.CreateMap(typeof(T), GetType());
}

public interface IMapTo<T>
{
    void MapTo(Profile profile) => profile.CreateMap(GetType(), typeof(T));
}

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(IMapFrom<>) || 
                                    i.GetGenericTypeDefinition() == typeof(IMapTo<>))))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);
            var mapTo = type.GetMethod("MapTo");
            var mapFrom = type.GetMethod("MapFrom");
            mapTo?.Invoke(instance, new object[] {this});
            mapFrom?.Invoke(instance, new object[] {this});
        }
    }
}

If the class implementing the interfaces overrides the default interface implementations, the MappingProfile class works as desired. However, if the classes simply rely on the default implementations, mapTo and mapFrom in the ApplyMappingsFromAssembly method are both null.

For example, this class would not have its mappings applied successfully:

public class CreateJobCommand : 
        UpdateJobInputModel, 
        IMapFrom<UpdateJobInputModel>,
        IMapTo<Job>,
        IRequest<int>
{

}

How can I get the default implementations if they're not reimplemented in the inheriting class?

1

There are 1 answers

5
JD Davis On

Per Kevin Gosse's comment to my question, I looked into using GetInterface().GetMethod() as seen in the Microsoft documentation.

If I take that approach, the now functional, resulting code looks like:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && (i.GetGenericTypeDefinition() == typeof(IMapFrom<>) || 
                                    i.GetGenericTypeDefinition() == typeof(IMapTo<>))))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);
            var mapTo = type.GetMethod("MapTo") ?? 
                        instance!.GetType()
                            .GetInterface("IMapTo`1")?
                            .GetMethod("MapTo");
            var mapFrom = type.GetMethod("MapFrom") ??
                            instance!.GetType()
                                .GetInterface("IMapFrom`1")?
                                .GetMethod("MapFrom");

            mapTo?.Invoke(instance, new object[] {this});
            mapFrom?.Invoke(instance, new object[] {this});
        }
    }
}