如何通过继承来订购课程?

时间:2017-05-18 10:05:00

标签: c# linq sorting reflection

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

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<Type>来订购此适配器列表:

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)=&gt; x,new TypeHierarchyComparer());

是:

AdapterAAABBCDNFHIJKPEGL,{ {1}},MOPQRS

是否应在TOPAA和{{1}之前订购正确的订单ABD {}}和S应该在TOB之前。似乎子类是在他们的父母之后订购的,但不一定是在他们的父母之后。

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

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<TSource, TKey>(this IEnumerable<TSource>, Func<TSource, TKey>, IComparer<TKey>)让匹配器首先应用于列表的前面。

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

2 个答案:

答案 0 :(得分:1)

问题在于你的任意排序规则过于随意。

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

因此,为了比较GB,您会找到最近的共同祖先(Adapter),然后找到这两种类型的下降类型({{1}然后在那些类型上执行任意排序规则。

像这样(没有关于代码质量的承诺: - )):

K

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

另外,您可能希望考虑&#34; memoizing&#34;祖先使用B进行查找。

答案 1 :(得分:1)

Linq OrderBy使用的排序算法(快速排序)需要完整的订单关系。您已使用未按部分顺序关系排序的对的任意赋值扩展了部分顺序,但结果不会满足完整排序的公理,特别是它不会是可传递的(例如在您的玩具示例中{ {1}}和P<AA,但AA<E当传递性需要E<P时),依赖于传递性的排序算法将会出现任意严重的行为。这就是你观察到的。不需要在内部使用快速排序的P<E,而是需要拓扑排序算法。