使用Linq的Where/Select过滤掉null并将类型转换为不可为空的方法不能做成扩展方法

Wat*_*ter 11 c# linq c#-8.0 nullable-reference-types

假设我有

List<MyObject?> list = ...;
Run Code Online (Sandbox Code Playgroud)

我想把它变成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!);
    }
Run Code Online (Sandbox Code Playgroud)

但是它不会转换IEnumerable<MyObject?>IEnumerable<MyObject>,我不确定为什么。这导致我出现如下错误:

[CS8619] 类型“列表”的值中引用类型的可空性与目标类型“列表”不匹配。

有没有办法让NotNull上面的功能以某种方式工作?

Eam*_*nne 16

这个问题与是否有一种方便的方法来过滤 C# 8.0 可空引用序列,只保留非空引用有很多重叠

那里发布的一个答案表现出最佳性能并且非常简洁,这里为后代重复了相关的编码片段:

public static class Extension {
    public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> o) where T:class
        => o.Where(x => x != null)!;
}
Run Code Online (Sandbox Code Playgroud)

尤其; 你没有需要Select只是删除?注释,我认为这是一个非常合理的地方放置一个非空!因为它很显然是正确的,并有可能集中。如果您真的关心 GC 性能,您可能会考虑将委托缓存在静态只读字段中,尽管这是否有意义更快是您需要衡量的事情。

如果您更喜欢通过 对非空声明采取零容忍方法!,那么另一个答案/sf/answers/4160430221/可能会很好。

  • 你能解释一下为什么不需要 `.Select()` 吗?对我来说这似乎是一个错误。根据我的理解,“!”应该在“IEnumerable&lt;T&gt;”上强制执行非空值,而不是在其元素上强制执行。 (4认同)
  • @TobiasKnauss - 是的,这很不幸,不是吗。可空性分析与其他类型不同;因此,围绕它的正常直觉,尤其是在一般情况下,并不总是有效。从外观上看,这些文档也不是很好。他们甚至只提到简单的情况。`!` 不会将某些内容标记为不可空;它完全“原谅”表达式中的可为空性,包括泛型类型参数。请注意,否则,将“List&lt;string&gt;”转换为“List&lt;string?&gt;”甚至可能是错误的,而不仅仅是相反。 (2认同)

Pav*_*ski 15

您必须将扩展方法更新为以下内容

public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> enumerable) where T : class
{
    return enumerable.Where(e => e != null).Select(e => e!);
}
Run Code Online (Sandbox Code Playgroud)

这里的重点是您将 的IEnumerable可为空的引用转换为不可空的引用,因此您必须使用IEnumerable<T?>. where T : class需要通用约束来帮助编译器区分可为空引用类型和Nullable<T>结构,您可以在此处阅读

由于可空引用类型和可空值类型的具体表示之间存在这个问题,任何使用 ofT?还必须要求您将 the 约束Tclassor 或struct

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

var list = new List<MyObject?>();
IEnumerable<MyObject> notNull = list.NotNull();
Run Code Online (Sandbox Code Playgroud)

  • 绕过编译时检查的不必要的运行时逻辑......非常脏。 (3认同)

ert*_*tan 8

您可以对 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);
    }
Run Code Online (Sandbox Code Playgroud)