由于我一直在整合Linq思维模式,我越来越倾向于通过IEnumerable<T>
泛型类型传递集合,这似乎构成了大多数Linq操作的基础。
但是我想知道,对于IEnumerable<T>
泛型类型的后期评估,如果这是一个好主意。使用T[]
泛型类型更有意义吗? IList<T>
?或其他什么?
编辑:以下评论非常有趣。有一件事虽然没有得到解决,但似乎是线程安全问题。例如,如果您对方法采用IEnumerable<T>
参数并且它在不同的线程中枚举,那么当该线程尝试访问它时,结果可能与那些意图传入的结果不同。更糟糕的是仍然,试图枚举IEnumerable<T>
两次 - 我相信抛出异常。我们难道不应该努力使我们的方法线程安全吗?
答案 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。