c#指示泛型成员的可空性
c# Indicate nullability of generic member
在这个设计的 C# 8 示例中:
#nullable enable
class Fred<T>
{
T Value; // If T is a nullable type, Value can be null.
public Fred() { }
public void SetValue(T value) { Value = value; }
public T GetValue() { return Value; }
public string Describe() { return Value.ToString() ?? "oops"; }
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George g = fredGeorge.GetValue();
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
我有三个设计目标:
- 如果我为 Fred 编写任何盲目假设 'Value' 永远不会为 null
的方法,编译器应该警告我
- 如果我在 Fred 之外(比如在 George 中)编写任何盲目假设 'GetValue' 永远不会 return null 的方法,编译器应该警告我。
- 没有编译器警告(如果我编写的代码不会盲目地假设值不会为空)
所以第一个版本不错,我在 Fred 中收到警告,Describe() 可能取消引用空引用(满足目标 #1),但我也收到警告,在 Fred 的构造函数中未初始化 Value (违反了目标#3)并且 George 在没有任何警告的情况下编译(违反了目标#2)。如果我进行此更改:
public Fred() { Value = default; }
George 仍然在没有警告的情况下进行编译(违反目标 #2),我在 Fred 的构造函数中收到关于 "Possible null reference assignment"(违反目标 #3)的不同警告。
我可以通过使用 null-forgiving 运算符摆脱可能的 null 引用赋值:
public Fred() { Value = default!; }
现在 Fred 只有正确的警告(Describe() 中可能取消引用),但 George 也没有警告就编译(违反了目标 #2)。
如果我尝试指出 'Value' 可以为空:
T? Value;
我得到一个编译器错误 "A nullable type parameter must be known to be a value type or non-nullable reference type" 所以这不好。
如果我回到
T Value;
并添加 "MaybeNull" 属性:
[return: MaybeNull]
public T GetValue() { return Value; }
我收到两条警告 - 一条在 Fred.Describe() 中,警告可能存在 null 取消引用(正确),另一条在 George 中,警告 fredGeorge.GetValue() 可能为 null(正确)。没有关于 fredFloat.GetValue() 为空(正确)的警告。
所以在添加代码以期待空引用之后,我最终得到的是:
class Fred<T>
{
T Value;
public Fred()
{
Value = default!;
}
public void SetValue(T value)
{
Value = value;
}
[return: MaybeNull]
public T GetValue()
{
return Value;
}
public string Describe()
{
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George? g = fredGeorge.GetValue();
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
这是该功能的正确模式吗?
在 System.Diagnostics.CodeAnalysis
中有一个属性 AllowNullAttribute
指定允许 null
作为输入,即使相应的类型不允许它。我会在您的最终示例中使用此属性来:
- 修饰字段
Value
.这将允许我们删除赋值[=17]中的null-forgiving operator
=].编译器不会警告我们 Possible null reference assignment
,因为现在它知道 null
值可以分配给 属性 Value
.
- 修饰方法
SetValue
的参数T value
。它将允许传递null
方法 SetValue
的值,而不会收到编译器警告 Cannot convert null literal to non-nullable reference type
。 (目前,如果我们将 null
值传递给方法 SetValue
,我们将收到此警告)
这是带有建议更改的最终示例:
class Fred<T>
{
// AllowNull attribute says that a null value
// can be assigned to the field Value.
[AllowNull]
private T Value;
public Fred()
{
// Now we can delete null-forgiving operator, because compiler knows
// that null value can be assigned to the field Value.
Value = default;
}
// AllowNull attribute says that a null value
// can be passed to the method SetValue.
public void SetValue([AllowNull] T value)
{
Value = value;
}
[return: MaybeNull]
public T GetValue()
{
return Value;
}
public string Describe()
{
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George? g = fredGeorge.GetValue();
// Compiler does not warn us "Cannot convert null literal to
// non-nullable reference type" because it knows that a null
// value can be passed to the method SetValue.
fredGeorge.SetValue(null);
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
如果我们使用常规 属性 而不是字段 Value
和一对方法 GetValue
和 SetValue
那么我们可以以更清晰的方式重写最终示例:
class Fred<T>
{
// Here we tell that:
// 1) a null value can be assigned;
// 2) a null value can be returned.
[AllowNull, MaybeNull]
public T Value { get; set; }
public Fred()
{
// Compiler does not warn us "Possible null reference assignment".
// It knows that a null value can be assigned. It is correct.
// We can delete null-forgiving operator.
Value = default;
}
public string Describe()
{
// If we delete null checking, then we get a warning "Dereference of
// a possibly null reference". It is correct. Compiler helps us to avoid
// NullReferenceException.
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
// Compiler warns us "Converting null literal or possible null
// value to non-nullable type". It is correct.
// We should use nullable reference type George?.
George g = fredGeorge.Value;
// Compiler does not warn us "Cannot convert null literal to
// non-nullable reference type". It knows that a null value
// can be passed to the method SetValue. It is correct.
fredGeorge.Value = null;
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.Value;
}
}
在这个设计的 C# 8 示例中:
#nullable enable
class Fred<T>
{
T Value; // If T is a nullable type, Value can be null.
public Fred() { }
public void SetValue(T value) { Value = value; }
public T GetValue() { return Value; }
public string Describe() { return Value.ToString() ?? "oops"; }
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George g = fredGeorge.GetValue();
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
我有三个设计目标:
- 如果我为 Fred 编写任何盲目假设 'Value' 永远不会为 null 的方法,编译器应该警告我
- 如果我在 Fred 之外(比如在 George 中)编写任何盲目假设 'GetValue' 永远不会 return null 的方法,编译器应该警告我。
- 没有编译器警告(如果我编写的代码不会盲目地假设值不会为空)
所以第一个版本不错,我在 Fred 中收到警告,Describe() 可能取消引用空引用(满足目标 #1),但我也收到警告,在 Fred 的构造函数中未初始化 Value (违反了目标#3)并且 George 在没有任何警告的情况下编译(违反了目标#2)。如果我进行此更改:
public Fred() { Value = default; }
George 仍然在没有警告的情况下进行编译(违反目标 #2),我在 Fred 的构造函数中收到关于 "Possible null reference assignment"(违反目标 #3)的不同警告。
我可以通过使用 null-forgiving 运算符摆脱可能的 null 引用赋值:
public Fred() { Value = default!; }
现在 Fred 只有正确的警告(Describe() 中可能取消引用),但 George 也没有警告就编译(违反了目标 #2)。
如果我尝试指出 'Value' 可以为空:
T? Value;
我得到一个编译器错误 "A nullable type parameter must be known to be a value type or non-nullable reference type" 所以这不好。
如果我回到
T Value;
并添加 "MaybeNull" 属性:
[return: MaybeNull]
public T GetValue() { return Value; }
我收到两条警告 - 一条在 Fred.Describe() 中,警告可能存在 null 取消引用(正确),另一条在 George 中,警告 fredGeorge.GetValue() 可能为 null(正确)。没有关于 fredFloat.GetValue() 为空(正确)的警告。
所以在添加代码以期待空引用之后,我最终得到的是:
class Fred<T>
{
T Value;
public Fred()
{
Value = default!;
}
public void SetValue(T value)
{
Value = value;
}
[return: MaybeNull]
public T GetValue()
{
return Value;
}
public string Describe()
{
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George? g = fredGeorge.GetValue();
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
这是该功能的正确模式吗?
在 System.Diagnostics.CodeAnalysis
中有一个属性 AllowNullAttribute
指定允许 null
作为输入,即使相应的类型不允许它。我会在您的最终示例中使用此属性来:
- 修饰字段
Value
.这将允许我们删除赋值[=17]中的null-forgiving operator
=].编译器不会警告我们Possible null reference assignment
,因为现在它知道null
值可以分配给 属性Value
. - 修饰方法
SetValue
的参数T value
。它将允许传递null
方法SetValue
的值,而不会收到编译器警告Cannot convert null literal to non-nullable reference type
。 (目前,如果我们将null
值传递给方法SetValue
,我们将收到此警告)
这是带有建议更改的最终示例:
class Fred<T>
{
// AllowNull attribute says that a null value
// can be assigned to the field Value.
[AllowNull]
private T Value;
public Fred()
{
// Now we can delete null-forgiving operator, because compiler knows
// that null value can be assigned to the field Value.
Value = default;
}
// AllowNull attribute says that a null value
// can be passed to the method SetValue.
public void SetValue([AllowNull] T value)
{
Value = value;
}
[return: MaybeNull]
public T GetValue()
{
return Value;
}
public string Describe()
{
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George? g = fredGeorge.GetValue();
// Compiler does not warn us "Cannot convert null literal to
// non-nullable reference type" because it knows that a null
// value can be passed to the method SetValue.
fredGeorge.SetValue(null);
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
如果我们使用常规 属性 而不是字段 Value
和一对方法 GetValue
和 SetValue
那么我们可以以更清晰的方式重写最终示例:
class Fred<T>
{
// Here we tell that:
// 1) a null value can be assigned;
// 2) a null value can be returned.
[AllowNull, MaybeNull]
public T Value { get; set; }
public Fred()
{
// Compiler does not warn us "Possible null reference assignment".
// It knows that a null value can be assigned. It is correct.
// We can delete null-forgiving operator.
Value = default;
}
public string Describe()
{
// If we delete null checking, then we get a warning "Dereference of
// a possibly null reference". It is correct. Compiler helps us to avoid
// NullReferenceException.
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
// Compiler warns us "Converting null literal or possible null
// value to non-nullable type". It is correct.
// We should use nullable reference type George?.
George g = fredGeorge.Value;
// Compiler does not warn us "Cannot convert null literal to
// non-nullable reference type". It knows that a null value
// can be passed to the method SetValue. It is correct.
fredGeorge.Value = null;
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.Value;
}
}