简体   繁体   中英

How to get the concrete implementation of an interface by the generic type?

I need some help figure out how to use reflection to get the concrete implementation based of the Dto type:

public interface IDocumentService<TDto>
{
}

public interface ICommentService: IDoumentService<CommentDto>
{
}

public abstract class DocumentService<TEntity,TDto>: IDocumentService<TDto> where TEntity: Entity, where TDto: Dto
{
}

public class CommentService: DocumentService<Comment,CommentDto>, ICommentService
{
}

So, what I want to do, is pass the CommentDto to a method so I can get to the CommentService.

public IDocumentService<TDto> GetDocumentService<TDto>()
{
    //based on the TDto type I want to find the concrete that 
    //implements IDocumentService<TDto>
}

I would call it like this:

var commentDocumentService = GetDocumentService<CommentDto>();

So, I would get back CommentService, knowing that I would only see the methods part of the IDocumentService interface.

Here is a possible implementation for GetDocumentService.

    public static IDocumentService<TDto> GetDocumentService<TDto>()
    {
        // Gets the type for IDocumentService
        Type tDto=typeof(IDocumentService<TDto>);
        Type tConcrete=null;
        foreach(Type t in Assembly.GetExecutingAssembly().GetTypes()){
            // Find a type that implements tDto and is concrete.
            // Assumes that the type is found in the executing assembly.
            if(tDto.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface){
                tConcrete=t;
                break;
            }
        }
        // Create an instance of the concrete type
        object o=Activator.CreateInstance(tConcrete);
        return (IDocumentService<TDto>)o;
    }

It wasn't clear whether you wanted to return a new object, so I assumed so.

EDIT:

Due to your comment, here is a modified version of GetDocumentService. The disadvantage is that you need to specify another type parameter. The advantage, though, is that this approach provides a certain degree of type safety, since both type parameters must be compatible.

    public static T GetDocumentService<TDto, T>() where T : IDocumentService<TDto>
    {
        // Gets the type for IDocumentService
        Type tDto=typeof(T);
        Type tConcrete=null;
        foreach(Type t in Assembly.GetExecutingAssembly().GetTypes()){
            // Find a type that implements tDto and is concrete.
            // Assumes that the type is found in the calling assembly.
            if(tDto.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface){
                tConcrete=t;
                break;
            }
        }
        // Create an instance of the concrete type
        object o=Activator.CreateInstance(tConcrete);
        return (T)o;
    }

EDIT 2:

If I understand correctly, you want to get the other interfaces implemented by the type of the return value of GetDocumentService. For example, GetDocumentService<CommentDto> returns an object of type CommentService that implements the ICommentService interface. If I understand correctly, the return value should be a Type object (for example, the return value could be typeof(ICommentService) ). Once you have the type, you should call the type's FullName property to get the type's name.

Use the following method on the return value of GetDocumentService to get the type of interface implemented by that value, for instance, typeof(ICommentService) .

    public static Type GetDocumentServiceType<TDto>(IDocumentService<TDto> obj){
        Type tDto=typeof(IDocumentService<TDto>);
        foreach(Type iface in obj.GetType().GetInterfaces()){
            if(tDto.IsAssignableFrom(iface) && !iface.Equals(tDto)){
                return iface;
            }
        }
        return null;
    }

Firstly, your CommentService class needs to be discoverable somehow, given the type of TDto . You can search all the loaded types from all the assemblies in the current AppDomain - however that will be painfully slow.

So you have the following viable options:

  • Use an attribute on the assembly that defines CommentService .
  • Use configuration to define this information.
  • Use MEF .

I'll demonstrate the first approach. Firstly create the attribute:

[AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)]
public sealed class DtoProviderAttribute : Attribute
{
    public Type ProvidedType { get; private set; }
    public Type ProviderType { get; private set; }

    public DtoProviderAttribute(Type providedType, Type providerType)
    {
        ProvidedType = providedType;
        ProviderType = providerType;
    }
}

And then apply it to the assembly that defines CommentService (typically you would put in AssemblyInfo.cs ).

[assembly:DtoProvider(typeof(CommentDto), typeof(CommentService))]

Now you can use those attributes to search for the concrete implementations.

public class ServiceFactory
{
    private static readonly Dictionary<RuntimeTypeHandle, Func<object>> _dtoMappings = new Dictionary<RuntimeTypeHandle, Func<object>>();

    public static IDocumentService<TDto> GetDocumentService<TDto>()
    {
        var rth = typeof(TDto).TypeHandle;
        Func<object> concreteFactory;
        lock (_dtoMappings)
        {
            if (_dtoMappings.TryGetValue(typeof(TDto).TypeHandle, out concreteFactory))
                return (IDocumentService<TDto>)concreteFactory();

            FillMappings();

            if (!_dtoMappings.TryGetValue(typeof(TDto).TypeHandle, out concreteFactory))
                throw new Exception("No concrete implementation found.");
            return (IDocumentService<TDto>)concreteFactory();
        }
    }

    private static void FillMappings()
    {
        // You would only need to change this method if you used the configuration-based approach.
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            var attrs = assembly.GetCustomAttributes(typeof(DtoProviderAttribute), false);
            foreach (DtoProviderAttribute item in attrs)
            {
                if (!_dtoMappings.ContainsKey(item.ProvidedType.TypeHandle))
                {
                    var expr = Expression.Lambda<Func<object>>(Expression.Convert(Expression.New(item.ProviderType), typeof(object)));
                    _dtoMappings.Add(item.ProvidedType.TypeHandle, expr.Compile());
                }
            }
        }
    }   
}

As 'Rune' pointed out: because of the cache the overhead of searching all the assemblies is low:

    private static void FillMappings()
    {
        foreach (var type in AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()).Where(x => x.IsClass && !x.IsAbstract))
        {
            foreach (var iface in type.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDocumentService<>)))
            {
                var arg = iface.GetGenericArguments()[0];
                if (!_dtoMappings.ContainsKey(arg.TypeHandle))
                {
                    var expr = Expression.Lambda<Func<object>>(Expression.Convert(Expression.New(type), typeof(object)));
                    _dtoMappings.Add(arg.TypeHandle, expr.Compile());
                }
            }
        }
    }

another possibility:

public IDocumentService<TDto> GetDocumentService<TDto>()
        {
            var genericParameter = typeof(TDto);

            return (from type in Assembly.GetExecutingAssembly().GetTypes()     // Get Types
                    where type.GetConstructor(Type.EmptyTypes) != null          // That is concrete
                    let interfaces = type.GetInterfaces()                       
                        from intf in interfaces
                    where intf.IsGenericType                                    // Which implement generic interface
                        let genarg = intf.GetGenericArguments()[0] 
                            where genarg == genericParameter                    // Where generic argument is of type genericParameter
                            select (IDocumentService<TDto>)                     // Cast to IDocumentService
                            Activator.CreateInstance(type)).FirstOrDefault();   // Instantiate
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM