使用 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&lt;MyObject&gt;,但是我无法删除可以为空的引用。

下面是一个 MCVE。在我的项目中,我将可空引用警告变成了错误,因此下面注释掉的行将无法编译。

如果我这样做.Where(e =&gt; e != null).Select(e =&gt; 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&lt;MyObject?&gt; 转换为IEnumerable&lt;MyObject&gt;,我不确定为什么。这导致我出现如下错误:

[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&lt;T?&gt;。需要where T : class 泛型约束来帮助编译器区分可空引用类型和Nullable&lt;T&gt; 结构,您可以阅读here

因为这个问题在 nullable 的具体表示之间 引用类型和可空值类型,T? 的任何使用也必须 要求您将T 限制为classstruct

之后,以下行将被编译而没有任何警告

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...)

Linq 中的 Groupby 和 where 子句

C#中LINQ与数据管道

是否有使用 Linq 动态创建过滤器的模式?

使用 LINQ 过滤列表