你能定义一个接受*任何*可为空的类型、值或引用的泛型吗?

Mar*_*eIV 6 c# generics nullable nullable-reference-types

在 C# 中使用新的可为空引用类型。很高兴看到他们从 Swift 中挖走了这个!这是一个非常棒的功能!但是...因为它本质上是“固定在”语言上的,所以我正在努力创建一个泛型,它可以采用任何可空类型,无论是值还是引用,这在 Swift 中都是微不足道的。

考虑这个类:

public abstract class LabeledValue<TValue> {
    public string? label { get; set; }
    public TValue? value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这是我想要实现的目标,以Int一个名为“Foo”的类为例:

public class LabeledInt : LabeledValue<Int>{}

var myLabeledIntA = new LabeledInt(){
    label = "Forty Four",
    value = 44
}

var myLabeledIntB = new LabeledInt(){
    label = "Not Set",
    value = null
}

public class LabeledFoo : LabeledValue<Foo>{}

var myLabeledFooA = new LabeledFoo(){
    label = "Set",
    value = new Foo()
}

var myLabeledFooB = new LabeledFoo(){
    label = "Not Set",
    value = null
}
Run Code Online (Sandbox Code Playgroud)

这抱怨我必须将 TValue 定义为可空。但是,我找不到可以解决可为空值类型(即 Int?)和可为空引用类型(即 Foo?)的约束。如何编写这样的约束?

这些都行不通...

public abstract class LabeledValue<TValue>
where TValue : Nullable {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

public abstract class LabeledValue<TValue>
where TValue : struct {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

public abstract class LabeledValue<TValue> {
    public string?          label { get; set; }
    public Nullable<TValue> value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我也尝试过这种想法,认为可空性可以作为实际类型参数传入,但随后它抱怨未设置“值”。

public abstract class LabeledValue<TValue> {
    public string? label { get; set; }
    public TValue  value { get; set; }
}

public class LabeledInt : LabeledValue<Int?>{}
Run Code Online (Sandbox Code Playgroud)

Mar*_*eIV 8

好的,找到了。您必须使用两个新的显式属性AllowNullMaybeNull

这是修改后的代码...

public abstract class LabeledValue<TValue> {

    public string? label { get; set; }

    [AllowNull, MaybeNull]
    public TValue value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

通过这一更改,我现在可以执行以下所有操作......

public class LabeledInt  : LabeledValue<int>{}
public class LabeledNInt : LabeledValue<int?>{}
public class LabeledFoo  : LabeledValue<Foo>{}
public class LabeledNFoo : LabeledValue<Foo?>{}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它们......

var a = new LabeledInt();
a.Value = 4;
a.value = null // This won't compile

var b = new LabeledNInt();
b.Value = 4;
b.Value = null; // This compiles just fine

var c = new LabeledFoo();
c.Value = new Foo();
c.Value = null; // This won't compile

var d = new LabeledNFoo();
d.Value = new Foo();
d.Value = null; // This compiles just fine
Run Code Online (Sandbox Code Playgroud)

注意:仍然有未初始化的警告Value,但这只是警告,不是错误。您必须确保Value在访问它之前显式设置非空类型。有点违背了使用可空/不可空类型的目的,但这更像是一种黑客行为,而不是真正的解决方案,这实际上是不可能的,因为可空值类型实际上是具体的,而可空引用类型Nullable<T>是常规引用类型,只是装饰有属性让编译器知道不接受空值。