关于可空性的一切在 C# 中都很棒。但是我们在分解代码的时候应该怎么处理呢?
想象一下,我们有一个大方法。它接收一些对象,检查重要字段是否为空,然后处理它们。在方法结束之前,编译器认为属性是非空的:
private class Person
{
public string? Name { get; set; }
}
//before
private void Do(Person person)
{
if (person.Name is null)
return;
Console.WriteLine(person.Name.Contains("John"));
Console.WriteLine(person.Name.Length);
}
这个方法很大,所以我们必须把它分解成一组小的方法:
//after
private void Do(Person person)
{
WriteIfNameIsJohn(person);
WriteNameLength(person);
}
private void WriteNameLength(Person person)
{
if (person.Name != null)
Console.WriteLine(person.Name.Length);
}
private void WriteIfNameIsJohn(Person person)
{
if (person.Name != null)
Console.WriteLine(person.Name.Contains("John"));
}
但是现在我们应该在每个新方法的开头检查属性是否为空!如果我们创建了三个方法——检查三次!对于每个“提取方法”操作,都需要添加空检查。
可能但冗长且不干净的解决方案是使用某种“已验证”类型并将其传递给新方法:
private class VerifiedPerson : Person
{
public override string Name { get; set; }
private VerifiedPerson(string name)
{
Name = name;
}
public static VerifiedPerson? GetVerifiedOrNull(Person person) =>
person.Name is { } name
? new VerifiedPerson(name)
: null;
}
如何以优雅的方式消除这些检查?也许有可能以某种方式传递对象属性的空状态?
谢谢)
一个简单的解决方案是不检查这些方法中的空值。这将验证留给了调用者,但由于所有方法都是私有的,所以应该是可以接受的。现在该对象仍在验证中,但只在一个地方。不过,您可以确保在没有首先验证参数的情况下不要调用这些辅助方法:
private void Do(Person person)
{
if (person?.Name != null)
{
WriteIfNameIsJohn(person);
WriteNameLength(person);
}
}
private void WriteNameLength(Person person)
{
Console.WriteLine(person.Name.Length);
}
private void WriteIfNameIsJohn(Person person)
{
Console.WriteLine(person.Name.Contains("John"));
}
如果验证检查很麻烦,可以将它们拉出到辅助方法中:
private bool IsValid(Person person)
{
if (person == null) return false;
if (person.Name == null) return false;
// etc...
return true;
}
private void Do(Person person)
{
if (IsValid(person))
{
WriteIfNameIsJohn(person);
WriteNameLength(person);
}
}
我同意 Rufus,没有人强迫你检查 null,特别是当所有这些方法都是私有的并且因此在你的控制之下时。在任何这些私有方法中,成员都不可能为空。
我们可以帮助编译器使用null-forgiving-operator
!
,例如:
private void WriteNameLength(Person person)
{
Console.WriteLine(person.Name!.Length);
}
但是我认为当你的逻辑如此庞大时,可能值得考虑从方法中提取一个新类并将名称作为该类的成员。然后,您可以在类的构造函数中轻松执行一次检查,而忘记所有私有方法中的任何空值。
//after
private void Do(Person person)
{
if(person.Name is null) return;
var writer = new NameWriter(person.Name);
writer.WriteIfNameIsJohn();
writer.WriteNameLength();
}
class NameWriter
{
private readonly string name; // name is not nullable here
public MyClass(string name) { this.name = name }
public void WriteNameLength()
{
// no need for any null-checks here
Console.WriteLine(this.name.Length);
}
}