【问题标题】:How to enforce rule-based object ordering when inserting into a list插入列表时如何强制执行基于规则的对象排序
【发布时间】:2012-03-27 08:50:13
【问题描述】:

我有一个接口 IRenderable 和一个类,该类管理呈现实现该接口的类的所有实例。

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

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

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


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


编辑:我希望的是一些管理规则并管理维护秩序的类,如下所示:

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:我如何从“ClassA 在 ClassB 之前”之类的规则转变为实例/类的明确顺序。
因缺乏完整规则而产生的歧义无关紧要,我只希望遵守现有规则。

【问题讨论】:

    标签: c# operator-precedence


    【解决方案1】:

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

    [Before(typeof(MyClass))]
    
    [After(typeof(MyClass))]
    

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

    那么你所要做的就是按正确的顺序对它们进行排序(我依靠算法专家来为这个算法提供一个花哨的算法名称)。

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

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

    HTH,

    巴布。

    【讨论】:

    • 感谢您的想法。用于编码规则的属性。这是我主要遇到问题的算法。
    【解决方案2】:

    渲染器能否确定所有类的顺序,即使是那些尚未实现的类?如果是这样,那么您可以创建一个IComparer&lt;IRenderable&gt;。如果你不知道未来实现类的期望顺序是什么,那么这并不比依赖实现者明确指定绘制顺序更可行!

    通常例如渲染时,您通常可以根据对象的属性确定绘制顺序,因此您可以有一个比较器,根据已知的渲染属性(例如透明度、ui-layer 等)来决定渲染顺序。

    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;   
       }
    }
    

    【讨论】:

    • 如果可能的话,我想避免使用特定的分层。我对您的第二种方法感兴趣,但不太遵循(我不想在同一类的多个实例之间强制排序,只是在不同类的实例之间进行排序)。
    • 这只是意味着检查类型,并返回零,除非你找到你所追求的类型差异。我将添加另一个示例,其中包含来自另一个答案中建议的基于属性的解决方案的提示。
    • 很抱歉不接受,但我创建了一个非常简单的测试,我认为它表明这种方法行不通(使用ICompare 接口)。请参阅已编辑的问题。
    • 很抱歉听到这个消息。出了什么问题?
    • 我做了一个测试项目来显示这个问题。我只是对字符串进行排序,而不是类型,但您可以看到结果。我认为主要问题是您不能在排序时仅将一项与另一项进行比较。算法必须更聪明一点。
    【解决方案3】:

    我已经设法找出一种似乎可行的方法(下面的完整代码示例)。

    基本上我会添加我的规则,然后我通过将规则中包含的所有内容逐个插入到列表中来分配可能的顺序,并在添加每个规则时遵守规则。项目在列表中的位置成为排序。当比较列表中的 2 件事(不在规则中)时,我只返回 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);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-09-10
      • 2015-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多