0

上一个问题讨论了 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);

我不介意这一点,但我正在寻找更好的想法。好像我正在添加一些应该已经存在的东西。

4

4 回答 4

2

问题在于你从哪里得到了集合(IEnumerable<T>)。如果您总是忙于检查null集合的值,则应该考虑修改源。例如:

public User GetUser(long id) { }
public List<User> GetUsers(long companyId) { }

第一种方法是有意义的,如果它null在没有找到用户时返回 a ,null返回值意味着not foundnull但在我看来,第二种方法在任何正常情况下都不应该返回 a 。如果没有找到用户,应该返回一个空列表,而不是一个null值,这意味着程序的某些内容不正确。在你的问题中给出的例子,如果没有找到文件,我不相信directoryInfo.GetFiles("*.txt")返回 null txt,而是应该返回一个空集合。

于 2011-09-21T08:45:11.110 回答
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);   

然后我添加了“安全”扩展,这样我可以使代码更短一些,以便于输入/更易于阅读

例如

 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);   
于 2011-09-21T09:09:50.057 回答
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>();
    }
}
于 2011-09-21T09:10:13.680 回答
-1

不幸的是,我不认为有任何内置的东西。除非你重复自己:

foreach(var item in (GetUsers() ?? new User[0])) // ...

一个稍微“更好”的实现(以 C# 编译器为yield returnsytnax 生成的内容为例),这稍微减少了内存浪费:

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() { }
}
于 2011-09-21T09:09:28.767 回答