【问题标题】:How to order classes by inheritence?如何通过继承对类进行排序?
【发布时间】:2017-05-18 10:05:00
【问题描述】:

我正在使用反射来检索可用类的列表,并且我想将输入对象调整为匹配的最特定类;即我想做类似的事情:

RawData input = new RawData() { ... };
Type targetAdapter = AdapterSelector.SelectAdapterFor(input);
AdapterSelector.TryGetAdaptMethod(targetAdapter, out Func<RawData, Adapter> fAdapt);
Adapter adapted = fAdapt(input);

每个适用的类都有以下签名:

public class Adapter
{
    public static Adapter Adapt(RawData input) { ... }
    public static bool Matches(RawData input) { ... }
}

可以使用反射来检索具有所需方法的类列表,如下所示1

public partial class AdapterSelector
{
    public static IEnumerable<Type> GetApplicableAdapterTypes()
    {
        IEnumerable<Type> allTypes = typeof(AdapterSelector).Assembly.GetTypes();
        IEnumerable<Type> applicableTypes = from t in allTypes
                                            where TryGetAdaptMethod(t, _) &&
                                                  TryGetMatchMethod(t, _)
                                            select t;
        return applicableTypes;
    }

    private static bool TryGetAdaptMethod(Type type, out Func<RawData, Adapter> adapt)
    {
        MethodInfo adaptMethod = type.GetMethod("Adapt", BindingFlags.Static|BindingFlags.Public, null, new Type[] { typeof(RawData) }, null);
        if (adaptMethod != null)
        {
            adapt = (Func<RawData, Adapter>)Delegate.CreateDelegate(typeof(Func<RawData, Adapter>), null, adapterMethod);
            return true;
        }
        else
        {
            adapt = null;
            return false;
        }
    }

    private static bool TryGetMatchMethod(Type type, out Func<RawData, bool> match)
    {
        MethodInfo matchMethod = type.GetMethod("Match", BindingFlags.Static|BindingFlags.Public, null, new Type[] { typeof(RawData) }, null);
        if (matchMethod != null)
        {
            match = (Func<RawData, bool>)Delegate.CreateDelegate(typeof(Func<RawData, bool>), null, matchMethod);
            return true;
        }
        else
        {
            match = null;
            return false;
        }
    }
}

我假设我可以使用以下IComparer&lt;Type&gt; 来订购此适配器列表:

public class TypeHierarchyComparer: IComparer<Type>
{
    public int Compare(Type lhs, Type rhs)
    {
        if (rhs.IsSubclassOf(lhs))
        {
            return -1;
        }
        else if (lhs.IsSubclassOf(rhs))
        {
            return +1;
        }
        else
        {
            // Return arbitrary but fixed value for siblings, cousins, etc.
            return lhs.FullName.CompareTo(rhs.FullName);
        }
    }
}

请注意,这是一种部分排序:如果一种类型是另一种类型的子类,则该类型应在 2 后者之后排序。如果两个类型都不是另一个类型的子类,则顺序未定义。

通过这种排序,我希望以下适配器层次结构能够正确排序3

  • Adapter
    • N
    • F
    • H
    • I
    • K
      • E
      • G
      • L
      • M
      • Q
      • R
    • O
    • B
    • C
    • J
    • P
      • AA
      • AB
      • D
      • S
      • T

但是,结果:

AdapterSelector.GetApplicableAdapterTypes().OrderBy((x) => x, new TypeHierarchyComparer());

是:

Adapter, AA, AB, B, C, D, N, F, H, I, @98654364@, @98654363@, @9865436@ , P, E, G, L, M, O, P, Q, R, S, @9876

正确的顺序是OP应该在AAABDST之前排序,O应该在B之前987654386@ 和 J。似乎子类是在其父母之一之后排序的,但不一定在其所有父母之后。

如何按特异性对类进行排序? (即在所有父类之前/之后对子类进行排序)


1)Assembly.GetTypes()Type.GetMethod(string, BindingFlags, Binder, Type[], ParameterModifier[])Can you get a Func<T> (or similar) from a MethodInfo object?Delegate.CreateDelegate(Type, object, methodInfo)

2)我正在使用Enumerable.OrderByDescending&lt;TSource, TKey&gt;(this IEnumerable&lt;TSource&gt;, Func&lt;TSource, TKey&gt;, IComparer&lt;TKey&gt;) 让匹配器首先应用在列表的前面。

3)Visual Studio 的解决方案资源管理器显示的类层次结构。我不能透露适配器的名称,因此我使用AAT,我保留了名称的字母顺序。

【问题讨论】:

  • 你得到的错误是当你比较 E 和 F 时。E 是 1 级,而 F 是 2 级。我建议在类中添加一个级别编号,这样你就只比较名称同一级别。

标签: c# linq sorting reflection


【解决方案1】:

问题是你的任意排序规则太随意了。

与其对类型名称进行排序(因此,比较GB,会说G 应该在B 之后),而是应该找到两个不相关类型的最接近的共同祖先。然后,您应该根据这两种类型下降的共同祖先的直接后代应用排序。

所以,为了比较GB,你找到最近的共同祖先(Adapter),然后找到这两种类型的后代类型(KB),然后对那些类型执行任意排序规则。


类似这样的事情(没有关于代码质量的承诺 :-)):

    public List<Type> Ancestors(Type any)
    {
        var result = new List<Type>();
        result.Add(any);
        while (any != typeof(object))
        {
            any = any.BaseType;
            result.Insert(0, any);
        }
        return result;
    }
    public int Compare(Type lhs, Type rhs)
    {
        if (rhs.IsSubclassOf(lhs))
        {
            return -1;
        }
        else if (lhs.IsSubclassOf(rhs))
        {
            return +1;
        }
        else
        {
            var lAncs = Ancestors(lhs);
            var rAncs = Ancestors(rhs);
            int ix = 0;
            while (lAncs[ix] == rAncs[ix])
            {
                ix++;
            }
            return lAncs[ix].FullName.CompareTo(rAncs[ix].FullName);
        }
    }

我认为这段代码不会在列表末尾徘徊,因为如果其中一个列表已用尽,这意味着其中一个类型 实际上是另一个类型的祖先,所以之前的子类化规则应该已经解决了比较。

此外,您可能希望考虑使用 Dictionary&lt;Type,List&lt;Type&gt;&gt; 来“记忆”祖先查找。

【讨论】:

  • 坏主意。你不会通过这样的技巧使关系传递。有专门为偏序设计的排序算法。
  • ...即使您碰巧使关系具有传递性,正如我承认您的黑客似乎所做的那样,您必须证明它,这有什么意义?只需使用拓扑排序即可。
  • @AntonTykhyy - 如果您意识到上面所做的大致相当于用包含其整个分层路径的字符串替换每个排序键(例如,用 o-&gt;Adapter-&gt;K-&gt;E 替换 E)和然后对这些字符串进行排序?找到祖先和直接导数就相当于跳到我们知道键开始不同的点。
  • 我已经意识到这一点,这就是我在上面添加第二条评论的原因。如果您谈论这些字符串,则结果关系是完整排序的证明变得足够明显,无需详细说明。我的观点是,你不能将任何偏序关系扩展到完整的顺序(想象一个带有合并的提交 DAG),有一类专门为偏序设计的排序算法,那么为什么不使用它们并学习一些新东西呢?顺便说一句,现在在这个问题中有两件事要学习:拓扑排序和将部分顺序扩展到完整顺序。
  • 或者,更确切地说,至少对于有限集,部分顺序可以扩展到完整顺序,但是 (a) 如何正确执行它可能并不明显- 这就是问题中发生的事情 - 并且(b)在偏序的框架中,产生的顺序可能没有“意义”。例如,对于提交 DAG,定义部分顺序“兼容”,其中子修订与父修订兼容。它可以扩展为一个完整的订单,但是当它们不兼容时,单独分支上的两个修订将是 &lt;&gt;
【解决方案2】:

Linq OrderBy(快速排序)使用的排序算法需要完整的顺序关系。您已经通过对未按偏序关系排序的对的任意分配扩展了偏序,但结果不会满足完整排序的公理,特别是它不会是传递性的(例如在您的玩具示例中@ 987654322@ 和 AA&lt;E,但是 E&lt;P 当传递性需要 P&lt;E) 并且依赖传递性的排序算法将表现任意糟糕。这就是你观察到的。您需要一个拓扑排序算法,而不是在内部使用快速排序的OrderBy

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多