我如何告诉编译器我的可空 C# 泛型约束?

How do I tell the compiler about my nullable c# generic constraints?

我有一个编辑器,可以让用户编辑对象(int、string、DateTime 等)的“简单”属性,因此它会遍历这些属性,然后为每个简单的 属性 支持编辑的对象:

#nullable enable

public class DataNode<T>
{
   protected PropertyInfo Prop;
   protected object Source;
   protected T Value;
   protected bool Modified;

   public DataNode(PropertyInfo prop, object source)
   {
      Prop = prop;
      Source = source;
      object? value = prop.GetValue(source);
      if (value is T t)
         Value = t;
      else
         Value = default;
      Modified = false;
   }

   public virtual bool IsValid()
   {
      return true;
   }

   public void Save()
   {
      if (Modified)
      {
         Prop.SetValue(Source, Value);
         Modified = false;
      }
   }
}

然后我有特定属性的 subclasses - 例如,如果有一个 int 属性 我不希望用户能够输入负值,我可以创建一个特定的子class:

public class NonNegativeIntNode : DataNode<int>
{
   public NonNegativeIntNode (PropertyInfo prop, object source)
   : base(prop, source)
   {
   }

   public override bool IsValid()
   {
      return Value >= 0;
   }
}

所以一切正常,在你问之前:

  1. 选择使用哪个 DataNode class 由属性上的自定义属性控制
  2. DataNode 的功能远不止于此,为简单起见我省略了这些。例如 BoolDataNode class 知道要编辑这个值,它应该使用复选框

问题是编译器抱怨“Value = default;”是一个可能的空引用赋值,并且 DataNode 构造函数可能以 'Value'.

的空值退出

我可以通过将 Value 定义为“protected T? Value”来让编译器满意,但这会在其他地方增加不必要的复杂性来检查 null 值,因为我非常清楚 BoolDataNode 内部的 Value 永远不会为 null。

我尝试将 DataNode 拆分为两个 classes - NullableDataNode 和 NonNullableDataNode - 但在 NonNullableDataNode 内部,即使我指定了“where T: notnull”,编译器仍然担心 'default'

这似乎是在说“where T: notnull”的意思是“我永远不会将 Value 设置为 null”,而我想告诉编译器的是“T 是一个不能为 null 的类型”(比如 bool)

有没有一种方法可以让编译器确信一切正常,而无需简单地使用 pragma 关闭所有可空性警告?

I can keep the compiler happy by defining Value as "protected T? Value" but that adds unnecessary complication elsewhere to check for a null value when I know darn well that inside BoolDataNode that Value will never be null.

正确的做法是将其设为 protected T? Value 字段,因为如果 T 是引用类型,您将分配 null。访问您的 Value 字段的人需要知道,如果 T 是 non-nullable 引用类型,他们可能仍会得到 null

T 不受约束时,您对 T? 的含义感到困惑。 T? 表示该值是“可默认的”,而不是“可空的”。这是一个细微的差别,但意味着您可以分配 default(T) 的值。另一种表达方式是:

If T is a reference type, T? means that you can assign null. If T is a value type, the ? in T? effectively has no meaning.

也就是说,DataNode<string>.Valuestring?类型,而DataNode<bool>.Valuebool类型,而不是bool?

(从技术上讲,当 T 不受约束并且是值类型时,没有办法 T?,意思是 Nullable<T>。对于可为空的值类型,编译器输出一个成员类型 Nullable<T> 而不是 T。但是,泛型由运行时而非编译器扩展。)


请注意,如果 T 被限制为值类型,这会发生变化:在这种情况下,T? 突然开始表示 Nullable<T>