【问题标题】:Nullability of reference types in return type doesn't match overridden member返回类型中引用类型的可空性与被覆盖的成员不匹配
【发布时间】:2019-11-02 15:45:30
【问题描述】:

我正在使用一个返回 JSON 的 API,其中一个值可以是 false 或对象。为了解决这个问题,我创建了一个自定义 JsonConverter<T>

internal class JsonFalseOrObjectConverter<T> : JsonConverter<T> where T : class
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.False)
        {
            return null;
        }
        else
        {
            return JsonSerializer.Deserialize<T>(ref reader);
        }
    }
}

问题是我得到以下编译器错误:

可能的空引用返回。

我可以将返回的类型设置为T?,但我会收到错误:

返回类型中引用类型的可空性与被覆盖的成员不匹配。

我该如何解决这个问题?

【问题讨论】:

  • 引用类型固有地可以为空。只有值类型(通常作为结构实现)需要明确给出。
  • 错误消息告诉您,您不能允许从此函数返回空值,因为您要覆盖的函数不允许空值返回值。我认为我们需要在这里查看您要覆盖的功能。
  • 我应该指定我已启用 C# 8 中的可为空引用类型。我要覆盖的方法是 docs.microsoft.com/en-us/dotnet/api/…
  • 你应该重新考虑你在做什么。无论使用哪种转换器,为 same 属性返回任意值的 API 都会使 每个 客户端感到困惑。 false 也是一个非常具体的 JSON 值,而不是 null 或缺失值。如果您无法更改 API,请考虑返回一个新的 T"empty" 实例而不是 null。
  • 您可以使用Option &lt;T&gt; 类型,如in this questionthis issue 中描述的类型,结合模式匹配并在属性为false 时返回None

标签: c# .net-core .net-core-3.0 c#-8.0 nullable-reference-types


【解决方案1】:

您已声明泛型类型是(不可为空的)T,但您返回的是null。这显然是无效的。

如果您不在乎,您需要让您的转换器实现 JsonConverter&lt;T?&gt; 或使用 null 宽恕运算符。

internal class JsonFalseOrObjectConverter<T> : JsonConverter<T?> where T : class
{
    public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        ...
    }

    public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
    {
        ...
    }
}

public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.TokenType == JsonTokenType.False)
    {
        return null!;
    }
    ...
}

【讨论】:

  • 两种解决方案都可以正常工作,但我真的很困惑。对于第一个,不是T吗?相当于 Nullable 这是一个可为空的结构?如何区分可空引用和可空结构?为什么是T?这里允许,但这里不允许JsonFalseOrConverter&lt;T?&gt;?其次,编译器如何允许对文字空值进行空值宽容。运算符的目的不是告诉编译器“别担心它不为空”。
  • T? 仅等效于 Nullable&lt;T&gt; 如果 T 是值类型。在您的示例中,它被限制为引用类型。没有为可空引用类型创建特殊类型,编译器只是为您进行额外检查以确保引用这些类型的任何代码都与其可空性一致。通常,您将无法在可空值类型和可空引用类型之间共享相同的泛型类,编译器无论如何都必须生成单独的类。
  • null 宽恕运算符允许您告诉编译器即使预期类型是不可为空的引用,您是说您想在此处使用可空值并且编译器不应该警告它。在幕后,一切仍然是相同的引用类型,只是编译器或多或少地严格限制了你的使用方式。
【解决方案2】:

最简单的解决方案是return null!

#nullable enable

internal class JsonFalseOrObjectConverter<T> : JsonConverter<T> where T : class
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.False)
        {
            return null!;
        }
        else
        {
            return JsonSerializer.Deserialize<T>(ref reader);
        }
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options){}
}    

引用类可为空的,因此当遇到T? 时,编译器只使用T

更好的选择是创建类似于 F# 的 Option type 的东西,如果设置了值,则包含 Some value,如果 value 为 false,则包含 None。通过将 Option 设为结构,即使属性缺失或为 null,我们也会获得默认的 None 值:

readonly struct Option<T> 
{
    public readonly T Value {get;}

    public readonly bool IsSome {get;}
    public readonly bool IsNone =>!IsSome;

    public Option(T value)=>(Value,IsSome)=(value,true);    

    public void Deconstruct(out T value)=>(value)=(Value);
}

//Convenience methods, similar to F#'s Option module
static class Option
{
    public static Option<T> Some<T>(T value)=>new Option<T>(value);    
    public static Option<T> None<T>()=>default;
}

如果遇到 false,反序列化器可以返回 None()default


internal class JsonFalseOrObjectConverter<T> : JsonConverter<Option<T>> where T : class
{
    public override Option<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.False)
        {
            return Option.None<T>(); // or default
        }
        else
        {
            return Option.Some(JsonSerializer.Deserialize<T>(ref reader));
        }
    }

    public override void Write(Utf8JsonWriter writer, Option<T> value, JsonSerializerOptions options)
    {
        switch (value)
        {
            case Option<T> (_    ,false) :
                JsonSerializer.Serialize(writer,false,options);
                break;
            case Option<T> (var v,true) :
                JsonSerializer.Serialize(writer,v,options);
                break;
        }
    }
}    

Write 方法展示了如何使用模式匹配处理Option&lt;T&gt;

使用此序列化程序,以下类:


class Category
{
    public string Name{get;set;}
}


class Product
{
    public string Name{get;set;}

    public Option<Category> Category {get;set;}
}

可以使用为缺少的类别生成的false 进行序列化:

var serializerOptions = new JsonSerializerOptions
{ 
    Converters = { new JsonFalseOrObjectConverter<Category>() }
};

var product1=new Product{Name="A"};
var json=JsonSerializer.Serialize(product1,serializerOptions);

这会返回:

{"Name":"A","Category":false}

反序列化此字符串会返回一个Product,其Category 是一个没有值的Option&lt;Category&gt;

var product2=JsonSerializer.Deserialize<Product>(json,serializerOptions);
Debug.Assert(product2.Category.IsNone);

模式匹配表达式可用于提取和使用具有值的类别的属性,例如:

string category=product2.Category switch { Option<Category> (_    ,false) =>"No Category",
                                        Option<Category> (var v,true)  => v.Name};

或者

if(product2.Category is Option<Category>(var cat,true))
{
    Console.WriteLine(cat.Name);
}

【讨论】:

    猜你喜欢
    • 2020-09-16
    • 2019-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-16
    • 2020-02-06
    • 1970-01-01
    相关资源
    最近更新 更多