即使参数具有非空约束,也会收到有关可空类型参数的错误

Posted

技术标签:

【中文标题】即使参数具有非空约束,也会收到有关可空类型参数的错误【英文标题】:Receiving error about nullable type parameter even when parameter has notnull constraint 【发布时间】:2020-02-02 09:38:16 【问题描述】:

我有一个通用接口IDataAdapter<T>;接口的实现者应该能够从数据源读取具有Guid ID 的 POCO。 IDataAdapter<T> 有一个方法 Read(Guid id),我想返回一个 T?,其中 null 表示在数据源中找不到匹配项。但是,即使在 IDataAdapter<T> 上使用约束 T : notnull,尝试定义此方法也会出现错误 CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint. 为什么我仍然会收到此错误,即使 T 约束为 notnull

代码(应在 C# 8 环境中使用<Nullable>enable</Nullable>):

interface IDataAdapter<T> where T : notnull

    T? Read (Guid id); // error CS8627

【问题讨论】:

Generic Constraint for Non Nullable types的可能重复 你用的是什么版本?网络核心 3.0 还是网络标准 2.1? @keysl 核心 3.0. @Manvinder Singh 我不认为这是重复的;该问题/答案早于 c#8 的可为空引用类型。 【参考方案1】:

我认为这个问题与this post 中发生的情况非常相似。

请注意,T? where T : classT? where T : struct 在 CLR 中的表示方式非常不同。前者只是 CLR 类型TCLR 中没有单独的类型来区分 TT? C# 中的 T? 只是增加了 C# 编译器的额外编译时间检查。另一方面,后者由CLR类型Nullable&lt;T&gt;表示。

让我们考虑一下你的方法:

T? Read (Guid id);

这应该如何在 CLR 中表示?返回类型是什么?编译器不知道T是引用类型还是值类型,所以编译器无法决定方法签名应该是:

T Read (Guid id);

或:

Nullable<T> Read (Guid id);

【讨论】:

【参考方案2】:

如果您不使用notnull 约束,则会引发相同的错误。您需要使用classstruct 约束来指定该类型。你不需要指定notnull,因为结构总是可以为空的,并且启用了可以为空的引用类型,类也是如此。

只需添加where T:classwhere T:struct

参考类型

如果添加class约束,例如:

#nullable enable

interface IDataAdapter<T>       
    where T:class

    T? Read (Guid id); // error CS8627
    
    void Something(T input);


class StringAdapter:IDataAdapter<string>

    public string Read(Guid id)=>id.ToString();
    
    public void Something(string input)


以下调用将产生警告:

var adp=new StringAdapter();
string? x=null;
adp.Something(x);  //CS8604: Possible null reference argument ....

值类型

另一方面,如果参数可以为空,则使用 struct 创建 IntAdapter 会导致编译错误:

interface IDataAdapter<T>       
    where T:struct

    T? Read (Guid id); // error CS8627
    
    void Something(T input);



class IntAdapter:IDataAdapter<int>

    public int? Read(Guid id)=>id.ToString().Length;
    
    public void Something(int input)


void Main()

    
    var adp=new IntAdapter();
    int? x=null;
    adp.Something(x);  //CS1503: Cannot convert from int? to int

这是因为编译生成的方法需要 int? 而不是 int

说明

原因是编译器必须在每种情况下生成非常不同的代码。对于一个类,它不需要做任何特别的事情。对于一个结构,它必须生成一个 Nullable

这在Try out Nullable Reference Types 的The issue with T? 部分中进行了解释:

可空值类型和可空引用类型之间的区别体现在如下模式中:

void M&lt;T&gt;(T? t) where T: notnull

这意味着参数是 T 的可空版本,并且 T 被限制为非空。如果 T 是一个字符串,那么 M 的实际签名将是 M([NullableAttribute] T t),但如果 T 是一个 int,那么 M 将是 M(Nullable t)。这两个签名本质上是不同的,而且这种差异是不可调和的。

由于可空引用类型和可空值类型的具体表示之间存在这个问题,T 有什么用?还必须要求您将 T 限制为类或结构。

【讨论】:

【参考方案3】:

如果您查看Nullable Struct 的文档,您会发现它必须是struct

public struct Nullable<T> where T : struct

我相信您需要将 T 限制为 struct

interface IA<T> where T : struct

    T? Read(Guid id);
    // Or Nullable<T> Read(Guid id);



class A : IA<int>

    public int? Read(Guid id)  Console.WriteLine("A"); return 0; 


顺便说一句。你能给我们举个例子,说明你想把这个类与什么类型一起使用吗?

为什么不直接使用where T: class 并返回T(甚至根本没有约束)?

interface IA<T> where T : class

    T Read(Guid id);

【讨论】:

以上是关于即使参数具有非空约束,也会收到有关可空类型参数的错误的主要内容,如果未能解决你的问题,请参考以下文章

请教一个unity有关于泛型参数的问题

可空引用类型 - 通过接受的参数返回类型可空性

即使我只有一个参数,我也会收到“包装承诺不可迭代错误”

kotlin 注释处理器中的可空类型

C#的未来:简化参数空值验证

Firebase,Swift:返回类型上的可空性说明符冲突,“可空”与现有说明符“非空”冲突