19

我已将我的项目升级到 netcore 3.0,并且我正在重构项目以使用新的可为空引用类型功能,但由于以下问题很快就卡住了。

假设我使用了一个返回以下 JSON 的 REST api:

{
  "Name": "Volvo 240",
  "Year": 1989
}

此 api 始终返回名称/年份,因此它们不可为空。

我会使用这个简单的类进行反序列化:

public class Car
{
    public string Name {get; set;}
    public int Year {get; set;}
}

我会Car使用 new 将其反序列化为一个实例System.Text.Json

var car = JsonSerializer.Deserialize<Car>(json);

这一切都有效,但是当启用可空引用类型时,我在声明为不可空但可以为空的Car类中收到警告。Name我明白为什么我会得到这个,因为可以在不初始化Name属性的情况下实例化这个对象。

所以理想情况下Car应该是这样的:

public class Car
{
    public string Name { get; }
    public int Year { get; }

    public Car(string name, int year)
    {
        Name = name;
        Year = year;
    }
}

但这不起作用,因为System.Text.Json序列化程序不支持带参数的构造函数。

所以我的问题是:我将如何声明CarName是不可为空的并让它在System.Text.Json没有“不可空”警告的情况下使用?

我不想让它可以为空,因为在启用可空引用类型时,我基本上必须对所有内容进行空检查,并且因为我的示例中的 REST API 说它们总是被提供,它们不应该是可空的。

4

3 回答 3

6

更新

System.Text.Json.NET 5 现在支持参数化构造函数,所以这应该不再是问题了。

请参阅https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-immutability?pivots=dotnet-5-0

下面的旧答案

在阅读了msdocs之后,我发现了如何解决这个问题。

因此,直到System.Text.Json无法在构造函数中使用参数实例化类,Car该类必须如下所示:

public class Car
{
    public string Name { get; set; } = default!;
    public int Year { get; set; }
}
于 2019-10-04T09:30:04.960 回答
2

更新 如果您在net5,请使用@langen 指出的现在提供的参数化构造函数支持。下面的其他内容仍然有用。

原创 稍微另类的方法。System.Text.Json使用私有无参数构造函数似乎没有问题。所以你至少可以做到以下几点:

public class Car
{
    public string Name { get; set; }
    public int Year { get; set; }

    // For System.Text.Json deserialization only
    #pragma warning disable CS8618 // Non-nullable field is uninitialized.
    private Car() { }
    #pragma warning restore CS8618

    public Car(string name, int year)
    {
        Name = name
            ?? throw new ArgumentNullException(nameof(name));
        Year = year;
    }
}

好处是:

  • 从您自己的代码中初始化对象必须通过公共 ctor。
  • 您不需要 = null!;对每个属性都执行此操作。

STJson 和可为空的引用类型的剩余缺点:

  • STJson 仍然需要属性的设置器在反序列化期间实际设置值。我尝试了私有的,但这是不行的,所以我们仍然无法获得不可变的对象......
于 2020-09-12T15:46:12.317 回答
0

另一种选择,对于那些想要处理具有有意义异常的缺失属性的人:

using System;

public class Car
{
    private string? name;
    private int? year;

    public string Name
    {
        get => this.name ?? throw new InvalidOperationException($"{nameof(this.Name)} was not set.");
        set => this.name = value;
    }

    public int Year
    {
        get => this.year ?? throw new InvalidOperationException($"{nameof(this.Year)} was not set.");
        set => this.year = value;
    }
}

于 2021-02-12T22:11:26.960 回答