【问题标题】:Is there a way to handle any type of collection, instead of solely relying on Array, List, etc?有没有办法处理任何类型的集合,而不是仅仅依赖 Array、List 等?
【发布时间】:2015-06-25 15:26:08
【问题描述】:

这个例子是一个名为“WriteLines”的方法,它接受一个字符串数组并将它们添加到异步文件写入器。它有效,但我很好奇是否有一种有趣的方式来支持 -any- 字符串集合,而不是依赖程序员转换为数组。

我想出了类似的东西:

public void AddLines(IEnumerable<string> lines)
{
    // grab the queue
    lock (_queue)
    {
        // loop through the collection and enqueue each line
        for (int i = 0, count = lines.Count(); i < count; i++)
        {
            _queue.Enqueue(lines.ElementAt(i));
        }
    }
    // notify the thread it has work to do.
    _hasNewItems.Set();
}

这似乎可行,但我不知道它有任何性能影响,或任何逻辑影响(订单会发生什么?我认为这将允许无序集合工作,例如HashSet)。

有没有更被接受的方法来实现这一点?

【问题讨论】:

  • 您可以发送任何IEnumerable&lt;string&gt;(即List&lt;string&gt;)的内容。 IEnumerable 不能保证顺序,所以如果这很重要,您应该考虑 IEnumerable 是否适合您的需求
  • 永远不要在 IEnumerable&lt;&gt; 上使用超过一个 LINQ 方法或多次枚举它,除非您对它的生成方式有强有力的保证。有些IEnumerable&lt;&gt; 会在您每次查看它们时重新构建...例如,如果您将File.ReadLines() 作为参数传递,则该文件将为lines.Count() 重新读取一次,并且为每个ElementAt 重新读取一次。跨度>

标签: c# performance collections


【解决方案1】:

您已收到IEnumerable&lt;string&gt; - 这意味着您可以对其进行迭代。哎呀,甚至还有一个语言功能专门 - foreach

foreach (string line in lines)
{
    _queue.Enqueue(line);
}

与您现有的方法不同,这只会对序列进行一次迭代。您当前的代码将根据底层实现的不同表现 - 在某些情况下 Count()ElementAt 已优化,但在某些情况下它们不是。如果您使用迭代器块并记录日志,您可以很容易地看到这一点:

public IEnumerable<string> GetItems()
{
    Console.WriteLine("yielding a");
    yield return "a";
    Console.WriteLine("yielding b");
    yield return "b";
    Console.WriteLine("yielding c");
    yield return "c";
}

尝试使用您当前的实现调用AddLines(GetItems()),然后查看控制台...

【讨论】:

  • 老实说,我避免使用 foreach,因为此代码用于 Unity,它运行的是旧版本的 Mono,它与 foreach 存在各种 GC 拆箱/装箱问题。尽管现在考虑它,但我实际上并不认为它适用于字符串。而且你的例子很有趣,我没想到。
  • @mGuv:你认为Count() 会比foreach 更有效地工作吗?
  • 天真地假设 Count() 只会返回一些用于存储当前长度的内部成员变量,类似于数组的做法:P
  • @mGuv:请注意Count() 只是一种扩展方法。它无权访问IEnumerable&lt;T&gt; 的任何额外成员。基本上唯一你可以用IEnumerable&lt;T&gt;做的事情是调用GetEnumerator(),然后处理结果......这正是foreach所做的。
  • @xanatos:确实。如果您向它传递List&lt;string&gt;,此代码框起来,因为底层迭代器将是List&lt;string&gt;.Enumerator - 但无论您做什么都会是这种情况。
【解决方案2】:

由于您使用的是线程,因此也添加此答案,请改用ConcurrentQueue,如下所示:

// the provider method
// _queue = new BlockingCollection<string>()
public void AddLines(IEnumerable<string> lines)
{
   foreach (var line in lines)
   {
     _queue.Add(line);
   }
}

不需要锁,并且允许多个消费者和提供者,因为我们为每个添加的元素进行标记。

消费者基本上只需要var workitem = _queue.Take();

【讨论】:

  • 当您使用ConcurrentQueue 时,使用信号量(或其他同步)有什么意义?
  • @Johnbot 所以消费线程可以进入睡眠状态?而不是一个活跃的while(!_queue.TryDequeue(out res)) 循环?
  • 当然,我在想BlockingCollection&lt;T&gt; 哪个by default 充当队列。 Take 是阻塞的,因此您不必滚动自己的外部同步。
  • @Johnbot Ty,不知道那种类型 :)
猜你喜欢
  • 2019-05-11
  • 2014-06-21
  • 1970-01-01
  • 1970-01-01
  • 2017-08-05
  • 1970-01-01
  • 1970-01-01
  • 2017-07-15
  • 2019-06-22
相关资源
最近更新 更多