Как обеспечить порядок объектов на основе правил при вставке в список - PullRequest
1 голос
/ 27 марта 2012

У меня есть интерфейс 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 before ClassB", к определенному порядку экземпляров/classes.
Неоднозначности, вызванные отсутствием полных правил, не должны иметь значения, я просто хочу придерживаться существующих правил.

Ответы [ 3 ]

1 голос
/ 27 марта 2012

Может ли средство визуализации определить порядок всех классов, даже тех, которые еще не реализованы?Если это так, то вы можете создать IComparer<IRenderable>.Если вы не знаете, какой желаемый порядок классов будет реализован в будущем, то это не более возможно, чем полагаться на то, что разработчики явно задают порядок отрисовки!

Обычно, например, для рендеринга, вы можете частоопределить порядок отрисовки из свойств объектов, чтобы вы могли иметь компаратор, который определяет порядок рендеринга на основе известных свойств рендеринга, таких как прозрачность, пользовательский слой и т. д.

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 на 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;   
   }
}
1 голос
/ 28 марта 2012

Мне удалось выяснить метод, который, кажется, работает (полный пример кода ниже).

В основном я добавляю свои правила, затем я назначаю все включенное в правило с возможным упорядочением, вставляя ихв список один за другим, соблюдая правила по мере добавления каждого из них.Позиция элемента в списке становится упорядоченной.При сравнении двух вещей в списке (не в правиле) я просто возвращаю 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);
        }
    }
}
1 голос
/ 27 марта 2012

Вы можете добавить два атрибута, которые будут принимать тип в качестве параметра и требовать, чтобы целевой класс был IRenderable.

[Before(typeof(MyClass))]

[After(typeof(MyClass))]

Тогда вам нужно будет выбрать все классы, которые реализуют IRenderable и получить эти атрибуты.

Тогда все, что вам нужно сделать, это отсортировать их в правильном порядке (я полагаюсь на алгоритма про, чтобы придумать причудливое имя алгоритма на этом).

Вы будетенужно решить, что делать, если результат неоднозначен (т.е. два класса следуют за одним и тем же).

Конечно, вы можете реализовать аналогичную логику через один и тот же интерфейс, но это немного выходит за рамкиIRenderable обязан, по моему мнению, поэтому я бы сделал еще один.

HTH,

Баб.

...