首页 > 解决方案 > C# 泛型可以支持 Class 或 Nullable 吗?

问题描述

在我目前的项目中,我有一Value<T>堂课。

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

我可以以任何方式创建一个允许我指定Value<string>or的 Value 类Value<int>,但效果是 ValueT对类有效,但T?对结构有效?

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

即,id 像 Compiler 支持以避免错误。

标签: c#genericsnullable

解决方案


我可以以任何方式创建一个Value允许我指定Value<string>or的类Value<int>,但其效果是 Value 适用T于类,但适用T?于结构?

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

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

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

但我们必须支持null价值观

null如果您使用代替default(T),则问题已解决。

  • 对于类类型,default(MyClass)有效地解析为null
  • 对于结构,default(MyStruct)返回一个结构,其属性都包含其默认值

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方法中,您可以看到无论您使用类还是结构,此通用调用堆栈的工作方式完全相同。

如果将容器更改为return input; 然后在这两种情况下ContainerReturnsNothing都会返回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为假,因为myStruct和分别包含(和)default(MyStruct)的不同值。IsConstructedtruefalse

MyStruct如果您使用原始示例中的类运行相同的检查,isNull将是正确的,因为所有属性都匹配,即myStruct两者default(MyStruct)都有一个Value属性设置为0.


推荐阅读