課題
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.
との指摘が出ます。string
とstring?
は属性の有無だけの差ですから型変換とはみなされず、そうなるのだと考えます。毎度の指摘抑止も大変ですから、拡張メソッドにするのが良さそうですね。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]);