【问题标题】:Getting the constructor of an Interface Type through reflection, is there a better approach than looping through types?通过反射获取接口类型的构造函数,有没有比遍历类型更好的方法?
【发布时间】:2011-03-05 02:27:02
【问题描述】:

我编写了一个通用类型:IDirectorySource<T> where T : IDirectoryEntry,我用它来通过我的接口对象管理 Active Directory 条目:IGroupIOrganizationalUnitIUser

这样我就可以写出以下内容:

IDirectorySource<IGroup> groups = new DirectorySource<IGroup>(); // Where IGroup implements `IDirectoryEntry`, of course.`

foreach (IGroup g in groups.ToList()) {
    listView1.Items.Add(g.Name).SubItems.Add(g.Description);
}

IDirectorySource&lt;T&gt;.ToList() 方法中,我使用反射来为类型参数T 找出合适的构造函数。然而,由于T 被赋予了一个interface 类型,它根本找不到任何构造函数!

当然,我有一个实现IGroup 接口的internal class Group : IGroup。无论我多么努力,我都无法弄清楚如何通过我的实现类将构造函数从我的接口中取出。

[DirectorySchemaAttribute("group")]
public interface IGroup {
}

internal class Group : IGroup {
    internal Group(DirectoryEntry entry) {
        NativeEntry = entry;
        Domain = NativeEntry.Path;
    }
    // Implementing IGroup interface...
}

在我的IDirectorySource&lt;T&gt;接口实现的ToList()方法中,查找T的构造函数如下:

internal class DirectorySource<T> : IDirectorySource<T> {
    // Implementing properties...
    // Methods implementations...
    public IList<T> ToList() {
        Type t = typeof(T)

        // Let's assume we're always working with the IGroup interface as T here to keep it simple.
        // So, my `DirectorySchema` property is already set to "group".
        // My `DirectorySearcher` is already instantiated here, as I do it within the DirectorySource<T> constructor.
        Searcher.Filter = string.Format("(&(objectClass={0}))", DirectorySchema)

        ConstructorInfo ctor = null;
        ParameterInfo[] params = null;

        // This is where I get stuck for now... Please see the helper method.
        GetConstructor(out ctor, out params, new Type() { DirectoryEntry });

        SearchResultCollection results = null;

        try {
            results = Searcher.FindAll();
        } catch (DirectoryServicesCOMException ex) {
            // Handling exception here...
        }

        foreach (SearchResult entry in results)
            entities.Add(ctor.Invoke(new object() { entry.GetDirectoryEntry() }));

        return entities;            
    }
}

private void GetConstructor(out ConstructorInfo constructor, out ParameterInfo[] parameters, Type paramsTypes) {
    Type t = typeof(T);

    ConstructorInfo[] ctors = t.GetConstructors(BindingFlags.CreateInstance
                                                | BindingFlags.NonPublic
                                                | BindingFlags.Public
                                                | BindingFlags.InvokeMethod);

    bool found = true;

    foreach (ContructorInfo c in ctors) {
        parameters = c.GetParameters();

        if (parameters.GetLength(0) == paramsTypes.GetLength(0)) {
            for (int index = 0; index < parameters.GetLength(0); ++index) {
                if (!(parameters[index].GetType() is paramsTypes[index].GetType()))
                    found = false;
            }
            if (found) {
                constructor = c;
                return;
            }
        }
    }

    // Processing constructor not found message here...
}

我的问题是T 永远是interface,所以它永远找不到构造函数。

有没有比遍历所有程序集类型更好的方法来实现我的接口?

我不关心重写我的一段代码,我想一开始就做好,这样我就不需要一次又一次地回来。

编辑#1

按照 Sam 的建议,我现在将遵循 INameName 约定。但是,是我自己还是有什么方法可以改进我的代码?

谢谢! =)

【问题讨论】:

    标签: c# generics reflection interface dependency-injection


    【解决方案1】:

    这里有几种可能性。

    • 山姆给了你一个答案。
    • 使用某种容器,请参阅Depencency injection
    • 将类型 T 约束为具有默认构造函数 (Constructor constraint) 并将 SetEntry(DirectoryEntry) 添加到 IDirectoryEntry 接口。
    • 重构您的代码,这样您的目录源就不会为创建新实例而负担。您可能有不同的搜索器,每个搜索器都返回正确的类型。
    
    class DirectorySource<T>: IDirectorySource<T>  {   
      public DirectorySource(ISearcher<T> searcher) {
        Searcher = searcher;   
      }   
      public IList<T> ToList()    {
        string filter = "...";
        return Searcher.FindAll(filter);   
      } 
    }     
    class GroupSearcher: ISearcher<Group> {
      public IList<Group> FindAll(string filter)    {
        entries = ...
        var entities = new List<Group>();
        foreach (var entry in entries) 
          entities.Add(new Group(entry.GetDirectoryEntry());
        return entities;   
      } 
    }
    

    然后您将像这样实例化您的 DirectorySource:

    IDirectorySource<Group> groups = new DirectorySource<Group>(new GroupSearcher());
    
    • ... :)

    编辑: 你也可以使用 lambda 表达式。

    class DirectorySource<T>: IDirectorySource<T> {
      // Or you could put the parameter in constructor if this is not the only place
      // where you create new instances of T
      public IList<T> ToList(Func<DirectoryEntry, T> create) {
        ...
        foreach (var entry in entries)
          entities.Add(create(entry.GetDirectoryEntry()));
        return entities;
      }
    }
    
    IList<Group> groups = new DirectorySource<Group>().ToList(entry => new Group(entry));
    

    关于类职责等,您是否对每个支持的类型进行特殊处理,例如。 Change(T) 方法(或任何其他方法)?如果是这样,那么我仍然会重构并使用知道如何处理适当类型的IDirectoryEntryManager&lt;T&gt;(或其他名称)。然后DirectorySource 可以使用该类来操作具体类型,而不会被不属于那里的细节所累。

    【讨论】:

    • DI 容器在我看来是最好的。使用 ninject,您可以简单地将所有类型绑定到接口并在构造函数中接收它们的列表。
    • @Patko:我必须有一个Searcher,它是System.DirectoryServices.DirectorySearcher 类的一个实例,在我的IDirectorySource&lt;T&gt; 通用接口中,因为我必须能够搜索任何 AD 架构的条目,难道不建议将 DirectorySearcher 包装在泛型类型中,而不是通过独立类进行吗?我不明白你向我展示的模式。你知道 Façade 设计模式吗?为了便于使用,库只有一个入口点,通过方法传递它的每一个特性。
    • 你的方法看起来很干净。
    • @Will:IDirectorySource&lt;T&gt;的具体职责是什么?通常设计你的类是一件好事,这样每个类都有一个责任,仅此而已。这就是所谓的单一责任原则。所以是的,也许你应该有 GroupDirectorySource,也许是一个包裹在 GroupSearcher 中的 DirectorySearcher,也许还有别的东西。目前只有你知道每个类的确切目的是什么,我们只能推测。
    • @Patko:感谢您提供这些指南。 IDirectorySource&lt;T&gt; 的职责是管理:创建、更改、删​​除和列出 Active Directory 条目,即表示组、用户或组织单位的模式。这些中的每一个都通过System.DirectoryServices.DirectoryEntry 类进行管理。每个 LDAP 条目/实体都与每个模式都可以使用每个属性完全相同。这就是为什么我认为适合使用泛型类型:IDirectorySource&lt;T&gt; 接口。我应该提一下我的灵感来自 Bart de Smet,他写了Linq to AD
    【解决方案2】:

    您可以遍历程序集中的所有类并找到实现该接口的类。当您发现多个实现时,您需要决定要做什么。

    或者如果你有一致的命名方案,你可以从接口名生成类名。

    【讨论】:

    • 你说的可以从接口名生成类名是什么意思?如果我理解正确的话,我想我不可能拥有class IGroup 以及拥有interface IGroup。虽然它作为代码非常通用,但我觉得某处有代码气味。我是不是用好方法来处理我的泛型类型?
    • 循环遍历程序集类型以查找我的接口的实现对我来说似乎有点矫枉过正。会有更好的方法吗?
    • @Will:Sam 的意思是,如果你有接口 IName,那么如果你遵循通常的命名方案,那么类名就是 Name。所以给定接口 IName 你只需删除 I 并有一个类名。当然,如果您愿意,您可以使用其他命名方案。
    • @Patko:感谢您的解释。这就是我所做的,尽可能地遵循命名约定。谢谢! =)
    • 查看程序集,或假设一个同名的类(减去 I)将有明显的设计缺陷迹象。它甚至不能解决问题,因为它要求所有可能的IGroup 实际上都是Group(这对于从外部IGroup 派生的任何类都不会成立)。几个问题。 IGroup 通常是一个空接口吗?为什么Group 必须是内部的?
    【解决方案3】:

    您可以创建特殊属性来指向实现类型,而无需依赖命名约定。

    [AttributeUsage(AttributeTargets.Interface)]
    public class ImplementingTypeAttribute: Attribute
    {
        public Type ImplementingType { get; set; }
    
        public ImplementingTypeAttribute(Type implementingType)
        {
            ImplementingType = implementingType;
        }
    }
    

    但是重构是个好主意:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-13
      • 2010-10-07
      • 1970-01-01
      • 1970-01-01
      • 2020-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多