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
为假,因为 myStruct
和 default(MyStruct)
包含 IsConstructed
的不同值(分别为 true
和 false
)。
如果您 运行 使用原始示例中的 MyStruct
class 同样的检查,isNull
将为真,因为所有属性都匹配,即 myStruct
和 default(MyStruct)
都有一个 Value
属性 设置为 0
.
在我当前的项目中,我有一个 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 specifyValue<string>
orValue<int>
, but where the effect is that Value holdsT
for classes, but holdsT?
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
为假,因为 myStruct
和 default(MyStruct)
包含 IsConstructed
的不同值(分别为 true
和 false
)。
如果您 运行 使用原始示例中的 MyStruct
class 同样的检查,isNull
将为真,因为所有属性都匹配,即 myStruct
和 default(MyStruct)
都有一个 Value
属性 设置为 0
.