A previous question讨论了IEnumerable以及使用空集合而不是空值集合的约定。这是一个很好的做法,因为它消除了许多容易出错的空检查。
但答案并未完全解决其中一个案例。很多时候我强制处理空值,特别是当从外部方法返回数组时。一个例子:
(foreignObj.GetPeople() ?? Enumerable.Empty<Person>())
.Where(p => p.Name != "John")
.OrderBy(p => p.Name)
.Take(4);
我编写了一个辅助方法,它确实提高了可读性。
public class SafeEnumerable
{
public static IEnumerable<T> From<T>(T[] arr)
{
return arr ?? Enumerable.Empty<T>();
}
}
导致:
SafeEnumerable.From(foreignObj.GetPeople())
.Where(p => p.Name != "John")
.OrderBy(p => p.Name)
.Take(4);
我不介意,但我正在寻找更好的想法。好像我正在添加应该存在的东西。
答案 0 :(得分:2)
问题找到了收集的位置(IEnumerable<T>
)。如果您总是忙于检查集合的null
值,则应考虑修改源。例如:
public User GetUser(long id) { }
public List<User> GetUsers(long companyId) { }
如果在找不到用户时返回null
,则第一种方法有意义,null
返回值表示未找到。但在我看来,第二种方法在任何正常情况下都不应该返回null
。如果找不到用户,则应返回空列表,而不是null
值,这意味着程序的某些内容不正确。在您的问题中给出的示例中,如果找不到directoryInfo.GetFiles("*.txt")
文件,我不相信txt
会返回null,而是应该返回一个空集合。
答案 1 :(得分:2)
我为IEnumerable创建了一系列扩展方法,第一个是EmptyIfNull
例如
public static class EnumerableExtensions {
public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> collection) {
return collection ?? Enumerable.Empty<T>();
}
}
这允许我做
var q = foreignObj.GetPeople().EmptyIfNull().Where(p=>p.Name != "John").OrderBy(p => p.Name).Take(4);
然后我添加了“安全”扩展程序,以便我可以缩短代码输入/更容易阅读
e.g。
public static IEnumberable<T> SafeWhere<T>(this collection<T> source,Func<T,bool> predicate) {
return source==null ? Enumerable.Empty<T>() : source.Where(predicate);
}
给
var q = foreignObj.GetPeople().SafeWhere(p=>p.Name != "John").OrderBy(p => p.Name).Take(4);
答案 2 :(得分:1)
如果您无法修改源以更正返回null的方法,那么您的方法是有意义的。
你也许可以把它变成一个扩展方法,这样它就可以用一种更惯用的LINQy方式:
var query = foreignObj.GetPeople()
.AsNonNullEnumerable()
.Where(p => p.Name != "John")
.OrderBy(p => p.Name)
.Take(4);
// ...
public static class EnumerableExtensions
{
public static IEnumerable<T> AsNonNullEnumerable<T>(this IEnumerable<T> source)
{
return source ?? Enumerable.Empty<T>();
}
}
答案 3 :(得分:-1)
不幸的是,我认为没有内置的东西。除非你重复一遍:
foreach(var item in (GetUsers() ?? new User[0])) // ...
稍微“更好”的实现(以C#编译器为yield return
sytnax生成的内容为例),略微减少浪费内存:
class SafeEnumerable<T> : IEnumerable<T>, IEnumerator<T>
{
private IEnumerable<T> _enumerable;
public SafeEnumerable(IEnumerable<T> enumerable) { _enumerable = enumerable; }
public IEnumerator<T> GetEnumerator()
{
if (_enumerable == null)
return this;
else
return _enumerable.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
public T Current { get { throw new InvalidOperationException(); } }
object System.Collections.IEnumerator.Current { get { throw new InvalidOperationException(); } }
public bool MoveNext() { return false; }
public void Reset() { }
public void Dispose() { }
}