【问题标题】:Can you define a generic that takes *any* nullable type, value or reference?你能定义一个接受 *any* 可为空的类型、值或引用的泛型吗?
【发布时间】:2020-01-14 17:47:20
【问题描述】:

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

考虑这个类:

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

这是我想要实现的目标,以 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
}

这抱怨我必须将 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; }
}

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

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

public class LabeledInt : LabeledValue<Int?>{}

【问题讨论】:

  • 您是否尝试在此处搜索现有答案? Nullable&lt;T&gt; 是结构和值类型,可空引用显然是引用类型。您应该使用classstruct 通用约束,就像在article 中的issue with T? 部分中解释的那样@
  • 如果你看上面,我试过了(最后一个答案在第二个到最后一个代码块。)是的,我在这里搜索过。当我没有找到任何可以回答这个问题的东西时,这就是我发布这个问题的原因。如果您知道解决方案,请随时在此处将其作为答案发布,如果有效,我将予以标记。
  • 你至少可以看看这个thread
  • TValue?TValue: classTValue: struct 从CLR 的角度来看是完全不同的东西时,可空引用类型使用属性进行注释,可空值类型是Nullable&lt;T&gt; 类型。你应该自己考虑,在这种情况下你想要什么。或者用前置条件和后置条件属性装饰类型
  • 是的,我刚刚发布了一个答案。惭愧,但我理解,因为它们被附加到 C# 上,而它们从一开始就是 Swift 的基本组成部分。在 Swift 中,无论 T 是类还是结构,可空变量都是具体的 Optional&lt;T&gt;,因此它们是相同的类型,因此是允许的。

标签: c# generics nullable nullable-reference-types


【解决方案1】:

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

这是修改后的代码...

public abstract class LabeledValue<TValue> {

    public string? label { get; set; }

    [AllowNull, MaybeNull]
    public TValue value { get; set; }
}

有了这个改变,我现在可以做以下所有事情......

public class LabeledInt  : LabeledValue<int>{}
public class LabeledNInt : LabeledValue<int?>{}
public class LabeledFoo  : LabeledValue<Foo>{}
public class LabeledNFoo : LabeledValue<Foo?>{}

然后像这样使用它们...

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

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

【讨论】:

    【解决方案2】:

    只是添加另一种方法来处理这个问题。你基本上写了两个版本的你想要做的任何事情......一个用于基于引用的版本,一个用于基于结构的版本。

    例如,这里有一个用于 C# 的 map 命令,它模拟了我在 Swift 中经常使用的函数。由于它依赖于两个都必须采用空值的泛型类型,因此我必须创建该函数的四个“版本”。

    // Class-Class version
    public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
    where T : class
    where U : class
        => (item != null)
            ? formatClosure(item)
            : null;
    
    // Struct-Struct version
    public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
    where T : struct
    where U : struct
        => item.HasValue
            ? formatClosure(item.Value)
            : null;
    
    // Class-Struct version
    public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
    where T : class
    where U : struct
        => (item != null)
            ? formatClosure(item)
            : null;
    
    // Struct-Class version
    public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
    where T : struct
    where U : class
        => item.HasValue
            ? formatClosure(item.Value)
            : null;
    

    是的,它很冗长,但同样需要它,因为可空引用类型与可空值类型不同。这使得语言透明,因为它可以处理所有变体。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多