如何通过泛型类型获取接口的具体实现?

4

我需要帮助弄清如何使用反射来获取基于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
这里有一个可能的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;
    }

不清楚您是否想要返回一个新对象,所以我假设是这样。

编辑:

根据您的评论,这是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;
    }

我该如何测试这个,因为我正在使用 GetCallingAssembly? 如果我有一个引用我的服务程序集的单元测试程序集,我该怎么做? - DDiVita
答案已经被编辑了。说实话,我之前并不知道在这里使用GetExecutingAssembly更好。 - Peter O.
哦,这个完美地解决了问题,我本来想把你的答案标记为最佳答案。我从Mohamed的回答中得到了GetExecutingAssembly()并在你的代码中运行了它。你能看到我在Mohamed Abed的回答中关于获取ICommentService接口的评论吗? - DDiVita
哦,实际上,我想返回ICommentService,因为它实现了IDocumentService<CommentDto>。我希望它足够通用,以便如果我传入另一个TDto类型,我将获取正确的接口。所以,我也可以有INoteService:IDocumentService<NoteDto>。如果我将NoteDto传递给GetDocumentService<NoteDto>,我希望能够得到INoteService。这有意义吗? - DDiVita
是的,那很有道理。我会尝试找到答案,但我猜想由于需要指定单独的返回类型,这可能是不可能的。可以通过类型约束(T GetDocumentService<TDto, T>() where T : IDocumentService<TDto>)实现类似的功能,但这将需要指定额外的类型参数。目前,您必须根据需要手动转换返回类型(例如,转换为 ICommentServiceINoteService)。 - Peter O.
我更希望找到一种方法,只返回ICommentService的名称并将其传递给Unity Resolver。因此,因为我们正在寻找IDocumentService<CommentDto>实现,所以我不能只找到实现它的接口吗? - DDiVita

1

首先,您的 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());
                }
            }
        }
    }

搜索所有类型时不需要变慢。第一次执行时,构建一个映射表。使用 Dto 类型作为键,服务作为值。每次后续查找的时间复杂度为 O(1)(而且常数相当低)。 - Rune FS
@Rune 谢谢 - 我今天可能有点笨。我已经更新了答案。 - Jonathan Dickinson

1

另一种可能性:

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
        }

你知道我如何根据TDo值获取ICommentService的名称吗?如果我使用Unity来实例化对象,我该如何仅提取接口名称? - DDiVita
我很抱歉取消将其标记为答案。实际上,我正在运行Peter O的代码,但是从您那里得到了GetExecutionAssembly方法的想法。您的代码对我始终返回null。 - DDiVita

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接