可以对属性进行排序操作吗?有更好的方法吗?

Is ok to perform ordering operation on Property? Is there a better way of doing it?

我需要一个按分数排序的集合并维护其位置编号。我是这样做的:

private readonly ICollection<Score> _winningTable = new List<Score>();

public IEnumerable<Score> WinningTable
{
    get
    {
        var orderedList = _winningTable.OrderByDescending(w => w.Score).ToList();
        orderedList.ForEach(w =>
        {
            var index = orderedList.IndexOf(w);

            // This will edit the object position and score
            w.EditScore((decimal)w.Score, index + 1);
        });

        return orderedList.ToList();
    }
}

在 属性 中,我正在维护订单并设置其位置 nbr。所以我只通过 属性 访问对象获胜分数。 我不确定这是否是正确的方法,我想知道是否有更好的方法。

不确定您使用 EditScore 方法的意图。您正在传递一个已经在实例内部的分数值。很高兴在您的问题中看到 class Score 的实现。

一般来说,我认为最好在方法和设置器中更新实例。我相信您的实例已通过某些代码更新了分数。

而且我怀疑你不需要index,据我所知,它是直接从 Score 派生的。

无论如何,下面是您的代码的更简单版本。

public IEnumerable<Score> WinningTable
{
    get
    {
        return _winningTable
            .OrderByDescending(w => w.Score)
            .Select((it, index) =>
            {
                it.EditScore((decimal)it.Score, index + 1);
                return it;
            })
           .ToList();
    }
}

如果你能摆脱方法EditScore(这是更可取的):

public IEnumerable<Score> WinningTable
{
    get
    {
        return _winningTable
           .OrderByDescending(w => w.Score)
           .ToList();
    }
}

这是我对您的要求的看法:

  1. 您需要 WinningTable 属性 始终表示您的分数的有序列表。
  2. 您需要每个分数在列表中保持其位置索引。

根据第二个要求,我假设每个分数在任何给定时间只能在一个 WinningTable 内。因为否则它的位置索引会是什么?

我认为您的实施存在一些问题:

  1. 没有什么能阻止您将单个 Score 对象添加到多个 WinningTables。在这种情况下,它的位置索引将不是您所期望的。

  2. 由于 惰性求值 IEnumerable 的性质,当我看到 IEnumerable 属性 时,我不要指望检索它是一项 costy 操作。但是,在您的 getter 中,您完全迭代了列表,这在您只需要检索 IEnumerable 时不会发生。换句话说,当我这样做时: var scores = obj.WinningTable; 我假设这将是一个 O(1) 操作,但在你的情况下它是 O(n^2) 因为你在 .ForEach 和如果你优化 .ForEach,由于排序的原因,它将是 O(nlogn)。

  3. 在 getter 中执行所有逻辑是违反直觉的。在调用 属性 之前,分数对象不会有正确的位置索引。考虑这种情况:

    var score = new Score(10);
    obj.AddScore(score);
    Console.WriteLine(score.Position); // ???
    

    由于您依赖 属性 的 getter 来执行您的逻辑,因此代码将无法运行。

我的建议是为您的目的创建一个修改后的集合,并在那里应用您的逻辑。以下是该集合的示例实现(为了演示,使用 Score class 的简化版本):

public class Score
{
    public int Value { get; set; }
    public int Position { get; set; } = -1;
}

public class SortedScoreCollection : Collection<Score>
{
    protected override void ClearItems()
    {
        foreach (var item in this)
        {
            item.Position = -1;
        }

        base.ClearItems();
    }

    protected override void InsertItem(int index, Score item)
    {
        if (item.Position != -1)
        {
            throw new InvalidOperationException("Unable to add score. A score can only be inside a single collection.");
        }

        index = this.FindInsertionIndex(item);

        item.Position = index + 1;

        for (int i = index; i < this.Count; i++)
        {
            this[i].Position++;
        }

        base.InsertItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
        this[index].Position = -1;

        for (int i = index + 1; i < this.Count; i++)
        {
            this[i].Position--;
        }

        base.RemoveItem(index);
    }

    protected override void SetItem(int index, Score item)
    {
        var oldItem = this[index];

        if (Equals(oldItem, item))
        {
            return;
        }

        if (item.Position != -1)
        {
            throw new InvalidOperationException("Unable to add score. A score can only be inside a single collection.");
        }

        this.RemoveItem(index);
        this.Add(item);
    }

    private int FindInsertionIndex(Score item)
    {
        int index = 0;
        while (index < this.Count && this[index].Value > item.Value)
        {
            index++;
        }

        return index;
    }
}

// Usage:
private readonly ICollection<Score> _winningTable = new SortedScoreCollection();

public IEnumerable<Score> WinningTable => _winningTable;

这样上面解释的所有问题都将得到解决,无论您如何从集合中添加或删除它们,您的集合和所有乐谱对象将始终处于一致状态。请注意,在此实现中,删除和插入将是 O(n) 操作,检索 WinningTable 将是 O(1) 操作。