8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

nullを取り除くLINQ Where [null許容参照型]

Last updated at Posted at 2020-04-30

課題

nullable reference type (null許容参照型) の配列からnullを取り除きたい

後述するように警告が出てしまうことが課題です。nullable生活を始めたら瞬時にこの問題に当たったのですが、巷になかなか情報が無いですね。

コード例

nullを除外しているにもかかわらずWhereの結果は IEnumerable<string?> なので、Selectのところで nullかも (Dereference of a possibly null reference) と警告が出ます。

#nullable enable

string?[] array =  { "hoge", null, "fuga", null, "piyo" };

var uppers = array
    .Where(s => s != null)
    .Select(s => s.ToUpper()) // 警告
    .OrderByDescending(s => s);

結論

今のところ、ここで示されている WhereNotNull という拡張メソッドを用意するのが一番確実に思いました。
https://github.com/dotnet/roslyn/issues/39586#issuecomment-547909968

static class MyExtensions
{
    public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) 
        where T : class 
    {
        if (source == null) 
        { 
            return Enumerable.Empty<T>(); 
        } 
  
        return source.Where(x => x != null)!;
    }
} 
var uppers = array
    .WhereNotNull()
    .Select(s => s.ToUpper())
    .OrderByDescending(s => s);

ほかの案

1.OfType

コメントで指摘頂きました。

var uppers = array
    .OfType<string>()
    .Select(s => s.ToUpper())
    .OrderByDescending(s => s);

ReSharperを利用している場合、Redundant 'IEnumerable.OfType<T>' call. Consider comparing with 'null' instead. との指摘が出ます。stringstring?は属性の有無だけの差ですから型変換とはみなされず、そうなるのだと考えます。毎度の指摘抑止も大変ですから、拡張メソッドにするのが良さそうですね。nullをフィルタする意図が即座には伝わりにくい点も緩和できます。

static class MyExtensions
{
    public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
        where T : class
        // ReSharper disable once RedundantEnumerableCastCall
        => source.OfType<T>();
}

2.びっくりSelect

個人的にこう呼んでいます。

var uppers = array
    .Where(s => s != null)
    .Select(s => s!)
    .Select(s => s.ToUpper())
    .OrderByDescending(s => s);

この例はあまりよくないので、この程度であればびっくりをいきなり付けていいですね。

var uppers = array
.Where(s => s != null)
.Select(s => s!.ToUpper())
.OrderByDescending(s => s);


せっかくnullableしているのにびっくり演算子付けたら負けな気がするし、かといって拡張メソッドはあまり作りたくないし、悩ましいところはあります。

## 3. SelectMany
あまりやりたくない案

```c#
var uppers = array.SelectMany(s => s == null ? new string[0] : new[] { s })
    .Select(s => s.ToUpper())
    .OrderByDescending(s => s);

この方法は、ほかには2回Parseしたくないときに使えたりします。

var array = new[] {"123", "abc", "一二三"};

// 素朴な方法
var numbers1 = array
    .Where(s => int.TryParse(s, out _))
    .Select(s => int.Parse(s));

var numbers2 = array
    .SelectMany(s => int.TryParse(s, out var i) ? new[] { i } : new int[0]);
8
4
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?