【问题标题】:C# - finite list or limited list?C# - 有限列表还是有限列表?
【发布时间】:2013-12-20 02:34:40
【问题描述】:

我想知道 C# 中的某个功能...

我想要List<Object> MyList();,我可以.Add(new Object()) 有限次数。假设我添加了 5 个元素,如果我要添加第六个元素,那么最后一个元素将被销毁,而这第六个元素将被放在列表的顶部。

c# 中是否有任何内置机制可以做到这一点?

【问题讨论】:

  • "last item" 相当含糊。你是指最新的还是最年长的?
  • 你的意思是像一个循环缓冲区吗?
  • 不存在,但有大量的实现。
  • 术语是Circular Buffer,没有原生支持。您需要在 Queue 周围编写一个自定义包装器
  • @kostyan 这不是循环缓冲区。循环缓冲区只是一个数组,其中包含指向应该被视为头部和尾部的指针;在大多数实现中,您将确保它不允许 允许人们在添加时覆盖其他项目。它可能是用于实现此概念的合适数据结构,但您可以使用循环缓冲区来实现其他行为,并且您可以使用其他底层实现来实现此行为。

标签: c#


【解决方案1】:

在我的核心库中,我有一个叫做LimitedQueue<T> 的东西。这可能与您所追求的相似(您可以轻松地将其修改为List<T>)。 (Source on GitHub)

using System.Collections.Generic;

namespace Molten.Core
{
    /// <summary>
    /// Represents a limited set of first-in, first-out objects.
    /// </summary>
    /// <typeparam name="T">The type of each object to store.</typeparam>
    public class LimitedQueue<T> : Queue<T>
    {
        /// <summary>
        /// Stores the local limit instance.
        /// </summary>
        private int limit = -1;

        /// <summary>
        /// Sets the limit of this LimitedQueue. If the new limit is greater than the count of items in the queue, the queue will be trimmed.
        /// </summary>
        public int Limit
        {
            get
            {
                return limit;
            }
            set
            {
                limit = value;
                while (Count > limit)
                {
                    Dequeue();
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the LimitedQueue class.
        /// </summary>
        /// <param name="limit">The maximum number of items to store.</param>
        public LimitedQueue(int limit)
            : base(limit)
        {
            this.Limit = limit;
        }

        /// <summary>
        /// Adds a new item to the queue. After adding the item, if the count of items is greater than the limit, the first item in the queue is removed.
        /// </summary>
        /// <param name="item">The item to add.</param>
        public new void Enqueue(T item)
        {
            while (Count >= limit)
            {
                Dequeue();
            }
            base.Enqueue(item);
        }
    }
}

你会这样使用它:

LimitedQueue<int> numbers = new LimitedQueue<int>(5);
numbers.Enqueue(1);
numbers.Enqueue(2);
numbers.Enqueue(3);
numbers.Enqueue(4);
numbers.Enqueue(5);
numbers.Enqueue(6); // This will remove "1" from the list
// Here, "numbers" contains 2, 3, 4, 5, 6 (but not 1).

【讨论】:

  • 你真的不应该扩展Queue或其他集合来修改这样的功能,因为你需要影响的方法不是virtual。将对象类型化为超类型的能力让您违反了这种类型打算持有的不变量,这通常是一个很大的问题。
  • 而且,正如我在另一篇文章中评论的那样:这将不允许随机删除。它被明确指定为List
  • @Servy 是的,但一般来说,如果您正在维护代码库(相对于允许来自第三方的插件或类似的东西),您会知道如何正确使用这个类,这很好。此外,如果 Microsoft 真的打算让人们不从基本集合类型扩展,他们将拥有sealed 他们。 ;)
  • @SpikeX 好吧,我真希望封印它们。鉴于没有一个有用的方法是虚拟的,因此扩展它们几乎没有用处。许多人并不真正理解阴影方法的语义,或者不会意识到/记住方法是阴影的,这可能会导致问题。与其说是对恶意代码的恐惧,不如说是开发人员犯错误的合理可能性,例如,将此集合传递给接受Queue 的方法,该方法会向其中添加一堆项目。 无意破解这段代码真的很容易。
  • 如果 Microsoft 真的打算让人们从 Queue&lt;T&gt; 扩展,他们会将 Enqueue 虚拟化。如果您将示例更改为Queue&lt;int&gt; numbers = new LimitedQueue&lt;int&gt;(5);,您可以添加任意数量的整数。
【解决方案2】:

正如Servy所说,Framework中没有内置集合。但是,您可以像这样制作一个 CircularBuffer -

namespace DataStructures
{
    class Program
    {
        static void Main(string[] args)
        {
            var buffer = new CircularBuffer<int>(capacity: 3);

            while (true)
            {
                int value;
                var input = Console.ReadLine();

                if (int.TryParse(input, out value))
                {
                    buffer.Write(value);
                    continue;
                }
                break;
            }

            Console.WriteLine("Buffer: ");
            while (!buffer.IsEmpty)
            {
                Console.WriteLine(buffer.Read());
            }
            Console.ReadLine();
        }
    }
}

namespace DataStructures
{
    public class CircularBuffer<T>
    {
        private T[] _buffer;
        private int _start;
        private int _end;

        public CircularBuffer()
            : this(capacity: 3)
        {
        }

        public CircularBuffer(int capacity)
        {
            _buffer = new T[capacity + 1];
            _start = 0;
            _end = 0;
        }

        public void Write(T value)
        {
            _buffer[_end] = value;
            _end = (_end + 1) % _buffer.Length;
            if (_end == _start)
            {
                _start = (_start + 1) % _buffer.Length;
            }
        }

        public T Read()
        {
            T result = _buffer[_start];
            _start = (_start + 1) % _buffer.Length;
            return result;
        }

        public int Capacity
        {
            get { return _buffer.Length; }
        }

        public bool IsEmpty
        {
            get { return _end == _start; }
        }

        public bool IsFull
        {
            get { return (_end + 1) % _buffer.Length == _start; }
        }
    }
}

以上代码来自PluralSight - Scott Allen's C# Generics

【讨论】:

    【解决方案3】:

    您可以使用固定大小的队列。之后只需调用 .ToList() 即可。

    Fixed size queue which automatically dequeues old values upon new enques

    【讨论】:

    • 那篇文章中的代码在我看来是低于标准的,它甚至没有扩展一个基本的 .NET 集合类。 (不过,我不是反对你的人。)
    • @SpikeX It's a good thing that it doesn't extend a base .NET collection. 当然,有些答案还有其他问题,比如锁定隐式对象引用。
    • @Servy Right,正如我后来了解到的那样。 ;)(很遗憾,我不能再编辑我的评论了。)
    【解决方案4】:

    没有一个内置集合会这样做,但是您可以轻松地创建自己的类,该类具有一个在添加项目时具有此行为的内部列表。这不是特别困难,但是写出标准列表将使用的所有方法并实现List 所做的所有接口可能有点乏味。

    【讨论】:

    • 最简单的方法是将添加到列表中的内容包装在一个方法中并从那里直接引导它。
    • @JeroenVannevel 如果这仅在相当小的范围内使用,并且全部在应用程序内部,那么可能是的。如果您想将集合公开给更广泛的代码库,甚至作为公共 API 的一部分,这将成为一个问题。
    猜你喜欢
    • 2016-01-06
    • 2011-04-04
    • 2011-01-07
    • 1970-01-01
    • 2018-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    相关资源
    最近更新 更多