Нулевые массивы и IEnumerable <T> - PullRequest
       31

Нулевые массивы и IEnumerable <T>

0 голосов
/ 21 сентября 2011

В предыдущем вопросе обсуждается 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 ]

2 голосов
/ 21 сентября 2011

Я создал серию методов расширения для 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);   
2 голосов
/ 21 сентября 2011

Проблема определяет, где вы получили коллекцию (IEnumerable<T>). Если вы всегда заняты проверкой null значений коллекции, вам следует рассмотреть возможность изменения источника. Например:

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

Первый метод имеет смысл, если он возвращает null, когда пользователя не найдено, возвращаемое значение null означает не найдено . Но второй метод, на мой взгляд, никогда не должен возвращать null при любых нормальных обстоятельствах. Если пользователи не найдены, должен быть возвращен пустой список вместо значения null, что означает, что что-то в программе неверно . И данный пример в вашем вопросе, я не верю, что directoryInfo.GetFiles("*.txt") возвращает ноль, если файл txt не найден, вместо этого он должен возвращать пустую коллекцию.

1 голос
/ 21 сентября 2011

Если вы не можете изменить источник, чтобы исправить метод, который возвращает нуль, тогда ваш подход имеет смысл.

Возможно, вы могли бы сделать его методом расширения, чтобы его можно было использовать в более идиоматическом, 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>();
    }
}
0 голосов
/ 21 сентября 2011

К сожалению, я не думаю, что для этого есть что-то встроенное.Если вы не повторяете себя:

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() { }
}
...