Как получить конкретную реализацию интерфейса по универсальному типу? - PullRequest
4 голосов
/ 12 сентября 2011

Мне нужна помощь, чтобы понять, как использовать отражение, чтобы получить конкретную реализацию, основанную на типе Dto:

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
{
}

Итак, что я хочу сделать, это передать CommentDto методу, чтобы я мог добраться до CommentService.

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

Я бы назвал это так:

var commentDocumentService = GetDocumentService<CommentDto>();

Итак, я вернусь к CommentService, зная, что увижу только часть методов интерфейса IDocumentService.

Ответы [ 3 ]

7 голосов
/ 12 сентября 2011

Вот возможная реализация для 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;
    }

Не было ясно, хотите ли вы вернуть новый объект, поэтому я предположил, что так.

EDIT:

Из-за вашего комментария, здесь есть модифицированная версия GetDocumentService. Недостатком является то, что вам нужно указать другой тип параметра. Однако преимущество заключается в том, что этот подход обеспечивает определенную степень безопасности типов, поскольку оба параметра типа должны быть совместимы.

    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;
    }

РЕДАКТИРОВАТЬ 2:

Если я правильно понимаю, вы хотите, чтобы другие интерфейсы были реализованы по типу возвращаемого значения GetDocumentService. Например, GetDocumentService<CommentDto> возвращает объект типа CommentService, который реализует интерфейс ICommentService. Если я правильно понимаю, возвращаемое значение должно быть объектом Type (например, возвращаемое значение может быть typeof(ICommentService)). Когда у вас есть тип, вы должны вызвать свойство FullName типа, чтобы получить имя типа.

Используйте следующий метод для возвращаемого значения GetDocumentService, чтобы получить тип интерфейса, реализованного этим значением, например, 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;
    }
1 голос
/ 12 сентября 2011

другая возможность:

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
        }
1 голос
/ 12 сентября 2011

Во-первых, ваш класс CommentService нужно как-то обнаружить, учитывая тип TDto. Вы можете искать все загруженные типы из всех сборок в текущем AppDomain - однако это будет мучительно медленно.

Итак, у вас есть следующие жизнеспособные варианты:

  • Используйте атрибут в сборке, который определяет CommentService.
  • Используйте конфигурацию для определения этой информации.
  • Использование MEF .

Я продемонстрирую первый подход. Сначала создайте атрибут:

[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;
    }
}

А затем примените его к сборке, которая определяет CommentService (обычно вы вводите AssemblyInfo.cs).

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

Теперь вы можете использовать эти атрибуты для поиска конкретных реализаций.

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());
                }
            }
        }
    }   
}

Как указал 'Rune': из-за кэша накладные расходы на поиск всех сборок низкие:

    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());
                }
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...