集合的参数/返回应该是IEnumerable <t>还是T []?</t>

时间:2008-12-28 18:49:48

标签: .net linq

由于我一直在整合Linq思维模式,我越来越倾向于通过IEnumerable<T>泛型类型传递集合,这似乎构成了大多数Linq操作的基础。

但是我想知道,对于IEnumerable<T>泛型类型的后期评估,如果这是一个好主意。使用T[]泛型类型更有意义吗? IList<T>?或其他什么?

编辑:以下评论非常有趣。有一件事虽然没有得到解决,但似乎是线程安全问题。例如,如果您对方法采用IEnumerable<T>参数并且它在不同的线程中枚举,那么当该线程尝试访问它时,结果可能与那些意图传入的结果不同。更糟糕的是仍然,试图枚举IEnumerable<T>两次 - 我相信抛出异常。我们难道不应该努力使我们的方法线程安全吗?

8 个答案:

答案 0 :(得分:14)

我经历了一个绕过T[]的阶段,长话短说,这是背后的痛苦。 IEnumerable<T>好多了

  

但我想知道,对于IEnumerable泛型类型的后期评估,如果这是一个好主意。使用T []泛型类型更有意义吗? IList的?或其他东西

后期评估正是IEnumerable如此优秀的原因。这是一个示例工作流程:

IEnumerable<string> files = FindFileNames();
IEnumerable<string> matched = files.Where( f => f.EndsWith(".txt") );
IEnumerable<string> contents = matched.Select( f => File.ReadAllText(f) );
bool foundContents = contents.Any( s => s.Contains("orion") );

对于不耐烦的人,这会获取文件名列表,过滤掉.txt个文件,然后如果任何文本文件包含单词foundContents,则将orion设置为true。

如果您使用上面的IEnumerable编写代码,则只会根据需要逐个加载每个文件。您的内存使用率非常低,如果您匹配第一个文件,则无需查看任何后续文件。太棒了。

如果您使用数组编写完全相同的代码,您最终会预先加载所有文件内容,然后才会(如果您有任何RAM)将扫描其中任何一个。希望这可以解释为什么懒惰列表如此优秀。

  

有一件事没有得到解决,但似乎是线程安全问题。例如,如果您对方法采用IEnumerable<T>参数并且它在不同的线程中枚举,那么当该线程尝试访问它时,结果可能与那些意图传入的结果不同。更糟糕的是仍然,试图枚举IEnumerable<T>两次 - 我相信抛出异常。我们难道不应该努力使我们的方法线程安全吗?

线程安全在这里是一个巨大的红鲱鱼。

如果您使用的是数组而不是可枚举,那么看起来就像它应该更安全,但事实并非如此。大多数情况下,当人们返回对象数组时,他们会创建一个新数组,然后将旧对象放入其中。如果你返回那个数组,那么那些原始对象就可以被修改,你最终会遇到你想要避免的线程问题。

部分解决方案是不返回原始对象的数组,而是返回新对象或克隆对象的数组,因此其他线程无法访问原始对象。这很有用,但是没有理由IEnumerable解决方案也不能这样做。一个不比另一个更安全。

答案 1 :(得分:11)

在大多数情况下,您不应该在T[]接口上传递数组(public)。有关详细信息,请参阅Eric Lippert的博客文章“Arrays considered somewhat harmful”。

异常是采用params数组的方法,因为它必须是一个数组。 (希望将来的版本允许在方法签名中使用“params IList<s> foo”。)

对于internal成员,做任何你喜欢的事;你可以控制界面的两面。

答案 2 :(得分:4)

输出类型应尽可能具体,输入类型应尽可能宽松。

所以请返回T[],但请输入IEnumerable<T>

你应该返回对方法有意义的东西。如果它需要做额外的工作来转换为另一种类型,那么只要不返回对内部数据结构的引用,只需删除它并返回内部数据结构,不要在自己的代码之外进行修改。

答案 3 :(得分:4)

“我们不应该努力使我们的方法线程安全吗?”

我们应该谨慎关于我们代码的abilities

仅仅因为一个方法不是线程安全并不意味着它是一个失败,只是你需要在尝试在多线程程序中使用它之前知道这个事实。您是否努力使Main()线程安全?线程安全在单线程程序中是否重要?

无论如何,我认为说“Foo是线程安全”并不是真的有意义,只是说“Foo具有以下在多线程环境中很重要的特性。”

“试图枚举IEnumerable两次 - 我相信会引发异常。”

那太糟糕了。幸运的是,你可以测试它,而不是扩展FUD。

我认为你所问的是“我不应该返回我的收藏品的副本,而不是引用内部会员收藏,这样来电者就不会修改我的数据”?答案是不合格的“也许”。有很多方法可以解决这个问题。

答案 4 :(得分:2)

  

但是我想知道,对于IEnumerable泛型类型的后期评估,如果这是一个好主意。使用T []泛型类型更有意义吗? IList的?或其他什么?

您需要注意所使用的术语。请注意类型的引用类型的实例之间的区别。

IE的数量只表示:“我可以通过调用GetEnumerator迭代这个集合。当我这样做时,每个元素都可以被称为T.”对于懒惰的评估,IE的数量没有任何说明。

考虑这三个声明:

//someInts will be evaluated lazily
IEnumerable<int> someInts = Enumerable.Range(0, 100);

//oddInts will be evaluated lazily
IEnumerable<int> oddInts = someInts.Where(i => i % 2 == 1);

//evenInts will be evaluated eagerly
IEnumerable<int> evenInts = someInts.Except(oddInts).ToList();

尽管所有三个的引用类型都是int的IEnumerable,但是只会非常评估evenInts(因为ToList枚举了它的目标并返回了一个List实例)。

至于手头的问题,我通常做的是将我的来电者视为期待一个热切评估的可枚举,所以我这样做:

public IEnumerable<int> GetInts()
{
  ...
  return someInts.ToList()
}

答案 5 :(得分:1)

我会在IList和IEnumerable之间做出选择,根本不会考虑数组。

请注意,除了作为扩展方法之外,IEnumerable没有Count或Length属性,而数组和IList则没有。所以,我的决定基于:

如果返回值具有已知数量的元素 - &gt; IList或数组(如果必须) 否则IEnumberable

答案 6 :(得分:0)

实现用C#编写的解释型编程语言,我还需要在类型为object []的返回值和类似Linq的函数的List之间进行选择,如map和filter。最后,我选择了类似Lisp的列表的惰性变体。有一个列表构造函数具有IEnumerable参数:z = new LazyList(...)。每当程序引用LazyList(head,tail或isempty)的一个属性时,枚举只被评估一步(head和isempty变得明确,tail变成另一个懒惰列表)。 lazylist相对于IEnumerable的优点是前者允许递归算法,并且(不得不)遭受多次评估。

答案 7 :(得分:0)

你总是可以为线程安全做到这一点,它可能会在很多实例中表现更好,因为没有锁定集合(它的副本)。请注意,这意味着您不是在集合上而是在成员上执行LINQ或foreach。

  public IEnumerable<ISubscription> this[string topic] {
            get  { 
                rwlock.EnterReadLock();
                try {
                    return subscriptionsByTopic[GetTopic(topic)].ToArray<ISubscription>();              
                    //thread safe
                }
                finally {
                    rwlock.ExitReadLock();
                }
            }
        }

也不要将IEnumerable用于SOA /多层应用程序,因为你无法序列化接口(不会带来很多痛苦).WCF可以更好地使用List。

相关问题