Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

reflection

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.

like image 375
DDiVita Avatar asked Jan 18 '23 18:01

DDiVita


2 Answers

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;
    }
like image 152
Peter O. Avatar answered Jan 31 '23 01:01

Peter O.


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());
                }
            }
        }
    }
like image 30
Jonathan Dickinson Avatar answered Jan 31 '23 01:01

Jonathan Dickinson