LoginSignup
4

More than 3 years have passed since last update.

posted at

updated at

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

課題

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

あまりやりたくない案

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]);

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
What you can do with signing up
4