使用Enumerable.Empty()的意外行为
本文关键字:意外 Enumerable Empty string 使用 | 更新日期: 2023-09-27 18:15:23
我希望Enumerable.Empty<string>()
返回字符串的空数组。相反,它似乎返回一个具有单个null
值的数组。这破坏了其他LINQ操作符,如DefaultIfEmpty
,因为可枚举对象实际上不是空的。这似乎没有记录在任何地方,所以我想知道我是否错过了什么(99%的可能性)。
GameObject类
public GameObject(string id,IEnumerable<string> keywords) {
if (String.IsNullOrWhiteSpace(id)) {
throw new ArgumentException("invalid", "id");
}
if (keywords==null) {
throw new ArgumentException("invalid", "keywords");
}
if (keywords.DefaultIfEmpty() == null) { //This line doesn't work correctly.
throw new ArgumentException("invalid", "keywords");
}
if (keywords.Any(kw => String.IsNullOrWhiteSpace(kw))) {
throw new ArgumentException("invalid", "keywords");
}
_id = id;
_keywords = new HashSet<string>(keywords);
}
测试 [TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void EmptyKeywords() {
GameObject test = new GameObject("test",System.Linq.Enumerable.Empty<string>());
}
看起来您期望这个条件:
keywords.DefaultIfEmpty() == null
表示true
。然而,如果源序列为空,DefaultIfEmpty
返回一个包含元素类型默认值的单例序列(在本例中为string
)。因此它将返回一个包含null
的序列。但它本身不是null
,所以条件返回false
。
你误解了DefaultIfEmpty
的实现,这里是参考源代码的实现。
public static IEnumerable<TSource> DefaultIfEmpty<TSource>(this IEnumerable<TSource> source) {
return DefaultIfEmpty(source, default(TSource));
}
public static IEnumerable<TSource> DefaultIfEmpty<TSource>(this IEnumerable<TSource> source, TSource defaultValue) {
if (source == null) throw Error.ArgumentNull("source");
return DefaultIfEmptyIterator<TSource>(source, defaultValue);
}
static IEnumerable<TSource> DefaultIfEmptyIterator<TSource>(IEnumerable<TSource> source, TSource defaultValue) {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) {
do {
yield return e.Current;
} while (e.MoveNext());
}
else {
yield return defaultValue;
}
}
}
所以它所做的是,如果IEnumerable<T>
不是空的,它只是返回IEnumerable<T>
,如果IEnumerable<T>
是空的,它返回新的IEnumerable<T>
,其中有一个对象,值为default(T)
。它将永远不会返回null
,这是您的测试正在测试的。如果您想测试这个,您需要执行
if(keywords.DefaultIfEmpty().First() == null)
然而,这将导致IEnumerable<string>
被计算多次。我会放弃LINQ,就像LINQ方法那样做,并且做得很长(这也消除了你在new HashSet<string>(keywords)
内部的额外评估)。
public GameObject(string id,IEnumerable<string> keywords)
{
if (String.IsNullOrWhiteSpace(id)) {
throw new ArgumentException("invalid", "id");
}
if (keywords==null) {
throw new ArgumentException("invalid", "keywords");
}
_keywords = new HashSet<string>();
using (var enumerator = keywords.GetEnumerator())
{
if (e.MoveNext())
{
do
{
if(e.Current == null)
throw new ArgumentException("invalid", "keywords");
_keywords.Add(e.Current);
} while (e.MoveNext());
}
else
{
throw new ArgumentException("invalid", "keywords");
}
}
_id = id;
}
这使得您只循环一次IEnumerable<string>
.
这解决你的问题了吗?
public GameObject(string id, IEnumerable<string> keywords) {
if (String.IsNullOrWhiteSpace(id)) {
throw new ArgumentException("invalid", "id");
}
if (keywords == null || !keywords.Any()
|| keywords.Any(k => String.IsNullOrWhiteSpace(k))) {
throw new ArgumentException("invalid", "keywords");
}
_id = id;
_keywords = new HashSet<string>(keywords);
}
*根据@ScottChamberlain &@ginkner