如何在插入列表时强制执行基于规则的对象排序

时间:2012-03-27 08:50:14

标签: c# operator-precedence

我有一个接口IRenderable和一个管理渲染实现接口的类的所有实例的类。

我的代码中有很多类,并期望其他人创建实现相同接口的类。

由于渲染的性质,我想说“在这个类的实例之前绘制这个类的实例”。

典型的方法是让每个类都实现DrawOrder属性,但是我不喜欢这个,因为类没有明确的绘制顺序值,重要的是相对顺序。如果我给每个类一个DrawOrder属性,那么实现该接口的任何人都需要知道所有类的值是什么。显然,如果很多人能够实现自己的课程,这是不可能的。


我想要的是能够定义规则“ClassA之前的ClassA,ClassA之前的ClassC”,然后在计算绘制顺序/添加实例时,我可以推断出正确的绘制顺序。实现该接口的其他人可以添加他们自己的内置实现和他们自己的添加规则。


编辑:我希望的是一些管理规则并管理维护订单的课程,如下所示:

class Renderer
{
    private List<Rule> Rules;

    private List<IRenderable> Renderables;

    // Adds to list of rules
    void EnforceBefore(Type FirstClass, Type SecondClass);

    // Inserts items ensuring all rules are followed.
    void Insert(IRenderable ClassInstance);

    void RenderAll();
}

然后,类可以根据需要添加规则(或者我可以使用返回它们的接口方法)。

以下是无效的快速测试

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<string> MyList = new List<string> { "wherever", "second", "first", "third", "first", "third", "second" };

        RuleBasedComparer<string> RuleComparer = new RuleBasedComparer<string>();

        // I want to ensure all instances of "first" appear in the list before all instances of "second"
        // and all instances of "second" appear in the list before all instances of "third".
        // I don't care where "wherever" appears (or anything beyond the above rules)

        RuleComparer.AddRule("first", "second");
        RuleComparer.AddRule("second", "third");

        MyList.Sort(RuleComparer);

        foreach (var item in MyList)
            Console.WriteLine(item);
        Console.ReadKey();
    }
}

public class RuleBasedComparer<T> : Comparer<T>
{
    private class OrderRule
    {
        public readonly T Before;
        public readonly T After;

        public OrderRule(T before, T after)
        {
            Before = before;
            After = after;
        }
    }

    private List<OrderRule> _Rules = new List<OrderRule>();

    public void AddRule(T before, T after)
    {
        _Rules.Add(new OrderRule(before, after));
    }

    public override int Compare(T x, T y)
    {
        // Find the applicable rule for this pair (if any)
        var ApplicableRule = _Rules.Where(or => or.After.Equals(x) && or.Before.Equals(y) ||
                                                or.After.Equals(y) && or.Before.Equals(x)).SingleOrDefault();

        if (ApplicableRule != null)
        {
            // If there is a rule then if x should be before y then return -1, otherwise return 1
            if (ApplicableRule.Before.Equals(x))
                return -1;
            else
                return 1;
        }
        else
        {
            // If no rule exists then say they are equal
            return 0;
        }
    }
}

TL; DR:我如何从类似“ClassB之前的ClassA”之类的规则转变为实例/类的明确排序。
由于缺乏完整的规则而产生的歧义无关紧要,我只想遵守现有的规则。

3 个答案:

答案 0 :(得分:1)

您可以添加两个属性,即将类型作为参数,并要求目标类为IRenderable

[Before(typeof(MyClass))]

[After(typeof(MyClass))]

然后,您必须获取实现IRenderable的所有类并检索这些属性。

然后你所要做的就是按照正确的顺序对它们进行排序(我依靠算法专业人员在这个上提出一个奇特的算法名称)。

如果结果不明确(例如:两个类在同一个类之后),你必须决定该怎么做。

当然你可以通过相同的界面实现类似的逻辑,但在我看来,这略微超出了IRenderable职责的范围,所以我会再做一个。

HTH,

巴布。

答案 1 :(得分:1)

渲染器能否确定所有类的顺序,即使那些尚未实现的类也是如此?如果是,那么您可以创建IComparer<IRenderable>。如果您不知道将来实现的所需类的顺序是什么,那么这比依赖实现者明确指定绘制顺序更不可行了!

通常用于例如渲染时,您通常可以从对象的属性确定绘制顺序,因此您可以使用比较器根据已知的渲染属性(如透明度,ui层等)来决定渲染顺序。

public class RenderOrderComparer : IComparer<IRenderable>
{
    public int Compare(IRenderable a, IRenderable b)
    {
       if (a.IsOverlay && !b.IsOverlay)
         return -1;
       if (b.IsOverlay && !a.IsOverlay)
         return 1,
       if (a.IsTransparent && !b.IsTransparent)
         return -1;
       if (b.IsTransparent && !a.IsTransparent)
         return 1;
       // ...and so on.

       return 0;
    } 
}

(注意,您应该考虑扩展Comparer而不是实现IComparer,请参阅MSDN Documentation on IComparer

另一个选项是更基于规则的每类解决方案,您可以通过实现IComparable接口来决定所有类。这使得每个类都有机会在其他(已知)类之前/之后进行自我排序。这里的缺点当然是它不能为它不知道存在的类提供规则。此外,您无法确定ClassA是否应该在ClassB之前呈现它,而ClassB表示它应该在ClassA之前呈现。这样的方案要求您维护所有类型的排序,以确保它是一致的。基本上你最终会在所有类中分布一个比较器实现,这可能是件坏事。

public interface IRenderable : IComparable<IRenderable>
{
    int Id { get; }
}

public class SomeRenderable : IRenderable
{
   public int CompareTo(IRenderable other)
   {
      if (other is SomeRenderable)
         return 0;
      if (other is OtherRenderableType)
         return 1;   
   }
}

答案 2 :(得分:1)

我已经设法找到一种似乎有效的方法(下面的完整代码示例)。

基本上我添加了规则,然后通过将规则中的所有内容逐个插入到列表中来分配规则中包含的所有内容,并在添加每个规则时观察规则。列表中项目的位置成为排序。当比较列表中的两件事情(不在规则中)时,我只返回0.

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        List<string> MyList = new List<string> { "second", "first", "second2", "wherever", "third", "second2", "third", "second", "first" };

        RuleBasedComparer<string> RuleComparer = new RuleBasedComparer<string>();

        // I want to ensure all instances of "first" appear in the list before all instances of "second" and "second2"
        // and all instances of "second" and "second2" appear in the list before all instances of "third".
        // I don't care where "wherever" appears (or anything beyond the above rules)

        RuleComparer.AddRule("first", "second");
        RuleComparer.AddRule("first", "second2");
        RuleComparer.AddRule("second", "third");
        RuleComparer.AddRule("second2", "third");

        MyList.Sort(RuleComparer);

        foreach (var item in MyList)
            Console.WriteLine(item);
        Console.ReadKey();
    }
}

public class RuleBasedComparer<T> : Comparer<T>
{
    private class OrderRule
    {
        public readonly T Before;
        public readonly T After;

        public OrderRule(T before, T after)
        {
            Before = before;
            After = after;
        }
    }

    private List<OrderRule> _Rules = new List<OrderRule>();

    private List<T> DesiredOrdering = new List<T>();

    private bool _NeedToCalculateOrdering = true;

    public void AddRule(T before, T after)
    {
        if (!_NeedToCalculateOrdering)
            throw new InvalidOperationException("Cannot add rules once this comparer has.");

        _Rules.Add(new OrderRule(before, after));
    }

    private void CalculateOrdering()
    {
        _NeedToCalculateOrdering = false;

        var ItemsToOrder = _Rules.SelectMany(r => new[] { r.Before, r.After }).Distinct();


        foreach (var ItemToOrder in ItemsToOrder)
        {
            var MinIndex = 0;
            var MaxIndex = DesiredOrdering.Count;

            foreach (var Rule in _Rules.Where(r => r.Before.Equals(ItemToOrder)))
            {
                var indexofAfter = DesiredOrdering.IndexOf(Rule.After);

                if (indexofAfter != -1)
                {
                    MaxIndex = Math.Min(MaxIndex, indexofAfter);
                }
            }

            foreach (var Rule in _Rules.Where(r => r.After.Equals(ItemToOrder)))
            {
                var indexofBefore = DesiredOrdering.IndexOf(Rule.Before);

                if (indexofBefore != -1)
                {
                    MinIndex = Math.Max(MinIndex, indexofBefore + 1);
                }
            }

            if (MinIndex > MaxIndex)
                throw new InvalidOperationException("Invalid combination of rules found!");

            DesiredOrdering.Insert(MinIndex, ItemToOrder);
        }
    }

    public override int Compare(T x, T y)
    {
        if (_NeedToCalculateOrdering)
            CalculateOrdering();

        if (x == null && y != null)
        {
            return -1;
        }
        else if (x != null && y == null)
            return 1;
        else if (x == null && y == null)
            return 0;

        // Find the applicable rule for this pair (if any)
        var IndexOfX = DesiredOrdering.IndexOf(x);
        var IndexOfY = DesiredOrdering.IndexOf(y);

        if (IndexOfX != -1 && IndexOfY != -1)
        {
            // We have a definite order
            if (IndexOfX > IndexOfY)
                return 1;
            else if (IndexOfX < IndexOfY)
                return -1;
            else
                return 0;
        }
        else if (IndexOfX != -1)
        {
            return -1;
        }
        else if (IndexOfY != -1)
        {
            return 1;
        }
        else
        {
            return 0; // Or maybe compare x to y directly
            //return Comparer<T>.Default.Compare(x, y);
        }
    }
}