使用 Linq 的 Where/Select 过滤掉 null 并将类型转换为不可为 null 不能做成扩展方法
Posted
技术标签:
【中文标题】使用 Linq 的 Where/Select 过滤掉 null 并将类型转换为不可为 null 不能做成扩展方法【英文标题】:Using Linq's Where/Select to filter out null and convert the type to non-nullable cannot be made into an extension method 【发布时间】:2020-04-13 08:09:14 【问题描述】:假设我有
List<MyObject?> list = ...;
我想把它变成List<MyObject>
,但是我无法删除可以为空的引用。
下面是一个 MCVE。在我的项目中,我将可空引用警告变成了错误,因此下面注释掉的行将无法编译。
如果我这样做.Where(e => e != null).Select(e => e!)
,那么在最新的 .NET Core 3.1.100 中就可以了,但是我无法将其提取到扩展方法中。
我尝试添加这个扩展方法
public static IEnumerable<T> NotNull<T>(this IEnumerable<T> enumerable)
return enumerable.Where(e => e != null).Select(e => e!);
但是它不会将IEnumerable<MyObject?>
转换为IEnumerable<MyObject>
,我不确定为什么。这导致我出现如下错误:
[CS8619] 类型“List”的值中引用类型的可空性与目标类型“List”不匹配。
有没有办法让上面的NotNull
函数以某种方式工作?
【问题讨论】:
你用的是什么版本的c#? c# 8 + 中允许为空的引用类型 + 编辑:我知道它在你的标签中,但我想确定一下。 dotnetfiddle.net/cKYl74 我的编译。 @Train .NET Core 3.1 启用了可为空引用和所有相关内容。我的也可以编译,但我不知道如何在源代码中设置将可为空的警告转换为错误。如果我在我的实际代码库中执行此操作,它将以警告作为错误进行编译。我将在上面添加一个注释,说明情况确实如此,但如果您知道如何通过编译指示将代码警告作为错误,请告诉我。 @Train 这很有趣,现在它在更新到最新的尖端 3.1 后可以工作,但是如果我尝试用一种方法抽象它会失败,但是 where/select 组合现在可以工作了。我将不得不编辑这个问题来改变它。 @Water 请看下面我的回答 【参考方案1】:您必须将您的扩展方法更新为以下内容
public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> enumerable) where T : class
return enumerable.Where(e => e != null).Select(e => e!);
这里的重点是您将可空引用的IEnumerable
转换为不可空引用,因此您必须使用IEnumerable<T?>
。需要where T : class
泛型约束来帮助编译器区分可空引用类型和Nullable<T>
结构,您可以阅读here
因为这个问题在 nullable 的具体表示之间 引用类型和可空值类型,
T?
的任何使用也必须 要求您将T
限制为class
或struct
。
之后,以下行将被编译而没有任何警告
var list = new List<MyObject?>();
IEnumerable<MyObject> notNull = list.NotNull();
【讨论】:
【参考方案2】:这个问题与Is there a convenient way to filter a sequence of C# 8.0 nullable references, retaining only non-nulls?有很多重叠
One answer posted there 表现出最好的性能并且非常简洁,相关的编码 sn-p 在这里为后代重复:
public static class Extension
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> o) where T:class
=> o.Where(x => x != null)!;
值得注意的是;您不需要需要到Select
只是为了删除?
注释,我认为放置可空性!
是一个非常合理的地方,因为它非常清楚正确并且可能集中。如果您真的关心 GC 性能,您可能会考虑将委托缓存在静态只读字段中,尽管这是否有意义地更快是您需要衡量的。
如果您更喜欢通过!
对非空声明采取零容忍方法,那么https://***.com/a/59434717/42921 的另一个答案可能会尽可能好。
【讨论】:
我会把!= null
改成is not null
【参考方案3】:
您可以对 Dotnet Core 3.1 的类和结构类型使用以下扩展。
[Pure]
public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> enumerable) where T : class
return enumerable.Where(e => e != null).Select(e => e!);
[Pure]
public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> enumerable) where T : struct
return enumerable.Where(e => e.HasValue).Select(e => e!.Value);
【讨论】:
【参考方案4】:如果您不想对编译器撒谎并且不使用 null-forgiving 运算符,则可以使用 Aggregate
方法解决问题:
static IEnumerable<TSource> WhereNotNull<TSource>(this IEnumerable<TSource?> source)
return source.Aggregate(
Enumerable.Empty<TSource>(),
(accumulator, next) => next == null ? accumulator : accumulator.Append(next));
请注意,这不是懒惰的操作。
如果您想要不使用 null-forgiving 运算符的惰性操作,您可以使用 foreach
:
static IEnumerable<TSource> WhereNotNull<TSource>(this IEnumerable<TSource?> source)
foreach (var element in source)
if (element != null)
yield return element;
【讨论】:
以上是关于使用 Linq 的 Where/Select 过滤掉 null 并将类型转换为不可为 null 不能做成扩展方法的主要内容,如果未能解决你的问题,请参考以下文章
C# LINQ 详解 From Where Select Group Into OrderBy Let Join
Linq to Entities基础之需要熟知14个linq关键字(from,where,select,group,let,on,by...)