C# 泛型可以支持 Class 或 Nullable 吗?

Can C# generics support Class or Nullable?

在我当前的项目中,我有一个 Value<T> class。

目前T可以是任何值,但我们必须支持Null值,所以目前我们以两种形式使用它: Value<string>Value<int?>

我能否以任何方式创建一个允许我指定 Value<string>Value<int> 的值 class,但效果是值保持 T classes,但对结构保持 T?

目标是避免开发人员指定 Value<int> 而我们后来遇到问题,因为我们没有正确处理 Null 值。

即,id 喜欢编译器支持以避免错误。

Can I in any way create a Value class that would allow me to specify Value<string> or Value<int>, but where the effect is that Value holds T for classes, but holds T? for structs?

不,因为在泛型类型约束中不能有“或”逻辑。

要在通用类型约束中实现“或”逻辑,您必须创建两个单独的通用 classes,每个都有自己的通用类型约束。请注意,这两个 classes 可以继承自一个共享的通用基础 class,它根本没有类型约束。
如果该基类型是抽象的,那么您可以确定消费者必须通过派生 classes 的类型约束检查之一(假设没有人添加其他派生 classes)

改变你的期望比试图让它成为你现在想要的方式更合适。

but we must support null values

如果您使用 default(T) 而不是 null,则问题已解决。

  • 对于 class 类型,default(MyClass) 有效解析为 null
  • 对于结构,default(MyStruct) return是一个结构,其属性都包含它们的默认值

default(MyStruct) 是使用“可空性”概念的常用方法,无需使用文字 null 值。

用法示例:

public class Container<T>
{
    private readonly Random _random = new Random();
    public T DoSomething(T input)
    {
        //always returns "nothing"
        return default(T);
    }
}

public class MyClass
{
    public int Value { get; set; }
}

public struct MyStruct
{
    public int Value { get; set; }
}

public class Test
{
    public bool ContainerReturnsNothing<T>(T input)
    {
        var container = new Container<T>();

        var output = container.DoSomething(input);

        return output.Equals(default(T));
    }

    public void TestAClassAndAStruct()
    {
        ContainerReturnsNothing(new MyClass()  {Value = 1}); // true
        ContainerReturnsNothing(new MyStruct() {Value = 1}); // true
    }
}

TestAClassAndAStruct 方法中,您可以看到无论您使用 class 还是结构,此通用调用堆栈的工作方式完全相同。

如果把容器改成return input;那么在这两种情况下,ContainerReturnsNothing 将 return false


您可能需要注意的一件事是,“无”结构无法与已创建但其属性都恰好具有默认值的结构区分开来。如果一个结构的属性恰好都有默认值,在你的情况下是一个有意义的(即不是什么)结构值,那么这是一个问题。

此处的一个简单示例是 int,其 default(int) 解析为 0。如果 0 是一个有意义的值,那么它因此是 not nothing,这意味着你不能用 0 来表示无。

但是你可以通过添加一个 属性 来解决这个问题,它在执行构造函数时强制包含一个非默认值:

public struct MyStruct
{
    public bool IsConstructed { get; } // default false
    public int Value { get; }

    public MyStruct(int myValue)
    {
        this.IsConstructed = true;
        this.Value = myValue;
    }
}
  • default(MyStruct) 将始终将 IsConstructed 设置为 false
  • 每个实例化的结构总是将 IsConstructed 设置为 true

这样就避免了这个问题,因为它们永远不会相等。换句话说:

var myStruct = new MyStruct(0);

var isNull = myStruct.Equals(default(MyStruct));

isNull 为假,因为 myStructdefault(MyStruct) 包含 IsConstructed 的不同值(分别为 truefalse)。

如果您 运行 使用原始示例中的 MyStruct class 同样的检查,isNull 将为真,因为所有属性都匹配,即 myStructdefault(MyStruct) 都有一个 Value 属性 设置为 0.