使用反序列化的C#8.0可空引用类型时的最佳实践?
我正在尝试C#8.0,我想为整个项目启用空引用检查。我希望我可以改进我的代码设计,并且不禁用任何代码范围中的可空性上下文 我在反序列化对象图时遇到了一个问题。对象之间有引用,但对于最终用户视图,对象图中的所有引用都必须有一个值 换句话说,在反序列化过程中,引用可以是使用反序列化的C#8.0可空引用类型时的最佳实践?,c#,.net-core,c#-8.0,nullable-reference-types,C#,.net Core,C# 8.0,Nullable Reference Types,我正在尝试C#8.0,我想为整个项目启用空引用检查。我希望我可以改进我的代码设计,并且不禁用任何代码范围中的可空性上下文 我在反序列化对象图时遇到了一个问题。对象之间有引用,但对于最终用户视图,对象图中的所有引用都必须有一个值 换句话说,在反序列化过程中,引用可以是null,但在所有对象完成加载后,最后一个过程将所有对象链接在一起,从而解析null引用 我已经能够使用两种不同的技术来解决这个问题,它们都按照预期工作。然而,它们也大大扩展了代码,而且它们看起来并不特别优雅 例如,我尝试为每种对象编
null
,但在所有对象完成加载后,最后一个过程将所有对象链接在一起,从而解析null
引用
我已经能够使用两种不同的技术来解决这个问题,它们都按照预期工作。然而,它们也大大扩展了代码,而且它们看起来并不特别优雅
例如,我尝试为每种对象编写一个成对的类,在反序列化过程中将它们用作中间对象。在这些成对的类中,所有引用都允许为null
。反序列化完成后,我复制这些类中的所有字段,并将它们转换为真实对象。当然,使用这种方法,我需要编写很多额外的代码
或者,我尝试放置一个可为null的字段和一个不可为null的属性。这与前面的方法类似,但我使用成对的成员而不是成对的类。然后我为每个字段添加一个内部setter。这种方法的代码比第一种方法少,但它仍然大大增加了我的代码库
传统上,在不考虑性能的情况下,我会使用反射来管理反序列化,这样每个类几乎没有额外的代码。但是编写自己的解析代码有一些好处,例如,我可以输出更有用的错误消息,包括关于调用方如何解决问题的提示
但是当我引入可为空的字段时,我的解析代码会大大增加。我想知道这是否是一个好的和适当的方法来做这件事
为了演示,我尽量简化了代码;我的实际课程显然远不止这些。我想知道是否有更好的代码风格
班级人员
{
私人IReadOnlyList?朋友;
内部人员(字符串名称)
{
this.Name=Name;
}
公共字符串名称{get;}
public-IReadOnlyList-Friends=>this.Friends!;
内部SetFriends(IReadOnlyList friends)
{
这个。朋友=朋友;
}
}
类PersonForSerialize
{
公共字符串?名称{get;set;}
公共IReadOnlyList好友{get;set;}
}
IReadOnlyList LoadPeople(字符串路径)
{
PersonForSerialize[]peopleTemp=LoadFromFile(路径);
人员[]人员=新人员[peopleTemp.Count];
对于(int i=0;i
由于C#8中不可为空的默认值,这一点当然会突出显示,但这确实是一个常见的“循环依赖”问题,在相互依赖的构造中一直存在
正如您所发现的,一个标准解决方案是使用受限setter。对于C#8,您可能希望使用“null宽恕”null代码>在对象图反序列化期间-允许您区分未构造集和有效空集,并最小化分配
例如:
class Person
{
internal Person(string name, IReadOnlyList<Person> friends)
{
Name = name; Friends = friends
}
public string Name { get; }
public IReadOnlyList<Person> Friends {get; internal set;}
}
class SerializedPerson { ... }
IEnumerable<Person> LoadPeople(string path)
{
var serializedPeople = LoadFromFile(path);
// Note the use of null!
var people = serializedPeople.Select(p => new Person(p.Name, null!));
foreach(var person in people)
{
person.Friends = GetFriends(person, people, serializedPeople);
}
return people;
}
班级人员
{
内部人员(字符串名称、IReadOnlyList好友)
{
姓名=姓名;朋友=朋友
}
公共字符串名称{get;}
公共IReadOnlyList好友{get;内部集合;}
}
类序列化Person{…}
IEnumerable LoadPeople(字符串路径)
{
var serializedPeople=LoadFromFile(路径);
//注意null的用法!
var people=serializedPeople.Select(p=>newperson(p.Name,null!));
foreach(人与人之间的变量)
{
person.Friends=GetFriends(person,people,serializedPeople);
}
还人,;
}
您担心的是,虽然对象不打算包含null
成员,但在构建对象图期间,这些成员将不可避免地为null
归根结底,这是一个非常普遍的问题。是的,它会影响反序列化,但也会影响对象的创建,例如数据传输对象或视图模型的映射或数据绑定。通常,在构造对象和设置其属性之间的很短时间内,这些成员将为null。其他时候,它们可能会在较长的时间内处于不确定状态,因为您的代码(例如)完全填充依赖项数据集,这是使用互连对象图所需的
幸运的是,微软已经解决了这个问题,为我们提供了两种不同的方法
选项#1:空原谅运算符
例如,第一种方法是使用。然而,不太明显的是,您可以直接在不可为空的成员上使用它,从而完全消除中间类(例如,在您的示例中,SerializedPerson
)。事实上,根据您的具体业务需求,您可以将Person
类简化为以下简单内容:
班级人员
{
内部人员(){}
公共字符串名称{get;internal set;}=null!;
公共IReadOnlyList Friends{get;internal set;}=null!;
}
选项2:可为空的属性
更新:从2021年3月9日发布的.NET 5.0.4(SDK 5.0.201)开始,以下方法现在将产生<
public class PersonForSerialize
{
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
[Obsolete("Only intended for de-serialization.", true)]
public PersonForSerialize()
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
{
}
// Optional constructor
public PersonForSerialize(string name, IReadOnlyList<string> friends)
{
Name = name;
Friends = friends;
}
public string Name { get; set; }
public IReadOnlyList<string> Friends { get; set; }
}
public List<Person> Friends { get; set; } = new List<Friends>();
using Newtonsoft.Json;
class Person
{
[JsonConstructor]
internal Person(
string name,
List<Person> friends)
{
Name = name; // Can end up being null if left without '?? "No name"';
Friends = friends ?? new List<Person>();
}
public string Name { get; }
public List<Person> Friends { get; set; }
public void AddFriends(IEnumerable<Person> friends)
{
Friends.AddRange(friends);
}
}
Deserialise<Person>("{}");
Deserialise<Person>("{ 'Name': 'John'}");
Deserialise<Person>("{ 'Name': 'John', 'Friends': [] }");
Deserialise<Person>("{ 'Name': 'John', 'Friends': [ {'Name': 'Jenny'}]}");
Deserialise<Person>("{ 'Friends': [{'Name': 'Jenny'}]}");