resetting/declaring 可为 null 的值类型的 new 与 null
new vs. null for resetting/declaring nullable value types
网上有很多关于如何在 C# 中使用可空值类型的示例。我还注意到,对于如何初始声明值、如何重置它以及在合并时如何使用它(与分配 'default/new' 值有关)似乎有不同的方法:
采用从各种在线资源 (Fiddle) 收集的以下示例:
Console.WriteLine("Initialize with null vs new");
int? b = null;
Console.WriteLine(b.HasValue);
int? c = new int?();
Console.WriteLine(c.HasValue);
Console.WriteLine("Reset as null vs new");
int? d = 123;
d = null;
Console.WriteLine(d.HasValue);
int? e = 123;
e = new int?();
Console.WriteLine(e.HasValue);
Console.WriteLine("Coalesce as null with cast vs new");
int? f = ("foo" == "bar") ? 123 : (int?)null;
Console.WriteLine(f.HasValue);
int? g = ("foo" == "bar") ? 123 : new int?();
Console.WriteLine(g.HasValue);
所有功能都没有问题,但我很想知道这两种方法是否存在技术差异?特别是,在幕后发生了什么不同的事情使得任何方法特别 suitable/unsuitable?
如果您不使用 null ,则不太清楚如何检查“无值”。检查 if b == null
之类的东西更清晰,更容易,因为大多数人使用该检查来查看是否有值。它可能会在用户期望空变量但在 new
之后不会出现的情况下产生错误。
调用new
不会分配内存,因为这是一个值类型。它将始终采用 int 的大小 + bool 的大小(bool 在那里让我们知道是否使用 hasValue
)
分配了一个值
编译器会将以下所有三种变体翻译成完全相同的 IL:
null
default
new T?()
没有分配,没有额外的内存。运行时和编译器会特殊处理可为 null 的值类型。
我们可以通过使用以下程序并查看翻译后的 c# 代码和 IL(注意 Console.WriteLine
用于确保编译器不会优化掉这些值)看到:
public static class Program {
public static void Main() {
int? j = A;
Console.WriteLine(j);
j = B;
Console.WriteLine(j);
j = C;
Console.WriteLine(j);
j = D;
Console.WriteLine(j);
}
public static int? A => 3;
public static int? B => null;
public static int? C => default;
public static int? D => new int?();
}
翻译如下:
public static class Program
{
public static Nullable<int> A
{
get { return 3; }
}
public static Nullable<int> B
{
get { return null; }
}
public static Nullable<int> C
{
get { return null; }
}
public static Nullable<int> D
{
get { return null; }
}
public static void Main()
{
Nullable<int> a = A;
Console.WriteLine(a);
a = B;
Console.WriteLine(a);
a = C;
Console.WriteLine(a);
a = D;
Console.WriteLine(a);
}
}
现在承认这是一个 反编译的 程序,但是如果你查看 IL,你会看到编译器 returns 每个 [=20] 的值相同=]、C
和 D
(仅在此处粘贴 B
的 IL 以保留 space,但请参阅下面的 SharpLab link):
method public hidebysig specialname static
valuetype [System.Private.CoreLib]System.Nullable`1<int32> get_B () cil managed
{
.maxstack 1
.locals init (
[0] valuetype [System.Private.CoreLib]System.Nullable`1<int32>
)
IL_0000: ldloca.s 0
IL_0002: initobj valuetype [System.Private.CoreLib]System.Nullable`1<int32>
IL_0008: ldloc.0
IL_0009: ret
}
如果你看不到或不知道 IL,有趣的部分是:
initobj valuetype [System.Private.CoreLib]System.Nullable`1<int32>
此默认值初始化结构。编译器和运行时都对可空类型进行特殊处理。 default initialized* 结构是 Nullable<>
表示 null
值的方式。
看到这个 SharpLab demo.**
现在如果我们删除属性并继续分配 null
等会怎样?
public static void Main() {
int? j = 3;
Console.WriteLine(j);
j = null;
Console.WriteLine(j);
j = default;
Console.WriteLine(j);
j = new int?();
Console.WriteLine(j);
}
编译器只是将 null
引用“强制转换”到结构类型***。
public static void Main()
{
Nullable<int> num = 3;
Console.WriteLine(num);
Console.WriteLine((Nullable<int>)null);
Console.WriteLine((Nullable<int>)null);
Console.WriteLine((Nullable<int>)null);
}
看到这个SharpLab demo。
我还想指出,调用 new T?()
并不像其他答案之一所暗示的那样进行分配。 Nullable<>
实际上是一个值类型。正如你在上面看到的 new int?()
默认初始化,所以没有分配。
总之都是一样的。这取决于个人喜好。我个人认为使用 null
是最好和最清晰的选择,我认为你很难找到持不同意见的人。根据上下文,我肯定会双重接管 new T?
并可能超过 default
。简单地说:你意味着null
,所以使用null
* 默认初始化不设置 HasValue
或 Value
属性,将它们分别保留为 false
和 default
,为我们提供 null
表示
** 如果您查看上述 Main
方法的 IL,请不要被 box
指令所迷惑。我们正在调用 Console.Writeline
的重载,它接受 object
从而强制对值类型进行装箱。
*** 众所周知,结构实际上不可能是 null
;这同样表明 由运行时和编译器进行特殊处理。
网上有很多关于如何在 C# 中使用可空值类型的示例。我还注意到,对于如何初始声明值、如何重置它以及在合并时如何使用它(与分配 'default/new' 值有关)似乎有不同的方法:
采用从各种在线资源 (Fiddle) 收集的以下示例:
Console.WriteLine("Initialize with null vs new");
int? b = null;
Console.WriteLine(b.HasValue);
int? c = new int?();
Console.WriteLine(c.HasValue);
Console.WriteLine("Reset as null vs new");
int? d = 123;
d = null;
Console.WriteLine(d.HasValue);
int? e = 123;
e = new int?();
Console.WriteLine(e.HasValue);
Console.WriteLine("Coalesce as null with cast vs new");
int? f = ("foo" == "bar") ? 123 : (int?)null;
Console.WriteLine(f.HasValue);
int? g = ("foo" == "bar") ? 123 : new int?();
Console.WriteLine(g.HasValue);
所有功能都没有问题,但我很想知道这两种方法是否存在技术差异?特别是,在幕后发生了什么不同的事情使得任何方法特别 suitable/unsuitable?
如果您不使用 null ,则不太清楚如何检查“无值”。检查
if b == null
之类的东西更清晰,更容易,因为大多数人使用该检查来查看是否有值。它可能会在用户期望空变量但在new
之后不会出现的情况下产生错误。调用
分配了一个值new
不会分配内存,因为这是一个值类型。它将始终采用 int 的大小 + bool 的大小(bool 在那里让我们知道是否使用hasValue
)
编译器会将以下所有三种变体翻译成完全相同的 IL:
null
default
new T?()
没有分配,没有额外的内存。运行时和编译器会特殊处理可为 null 的值类型。
我们可以通过使用以下程序并查看翻译后的 c# 代码和 IL(注意 Console.WriteLine
用于确保编译器不会优化掉这些值)看到:
public static class Program {
public static void Main() {
int? j = A;
Console.WriteLine(j);
j = B;
Console.WriteLine(j);
j = C;
Console.WriteLine(j);
j = D;
Console.WriteLine(j);
}
public static int? A => 3;
public static int? B => null;
public static int? C => default;
public static int? D => new int?();
}
翻译如下:
public static class Program
{
public static Nullable<int> A
{
get { return 3; }
}
public static Nullable<int> B
{
get { return null; }
}
public static Nullable<int> C
{
get { return null; }
}
public static Nullable<int> D
{
get { return null; }
}
public static void Main()
{
Nullable<int> a = A;
Console.WriteLine(a);
a = B;
Console.WriteLine(a);
a = C;
Console.WriteLine(a);
a = D;
Console.WriteLine(a);
}
}
现在承认这是一个 反编译的 程序,但是如果你查看 IL,你会看到编译器 returns 每个 [=20] 的值相同=]、C
和 D
(仅在此处粘贴 B
的 IL 以保留 space,但请参阅下面的 SharpLab link):
method public hidebysig specialname static
valuetype [System.Private.CoreLib]System.Nullable`1<int32> get_B () cil managed
{
.maxstack 1
.locals init (
[0] valuetype [System.Private.CoreLib]System.Nullable`1<int32>
)
IL_0000: ldloca.s 0
IL_0002: initobj valuetype [System.Private.CoreLib]System.Nullable`1<int32>
IL_0008: ldloc.0
IL_0009: ret
}
如果你看不到或不知道 IL,有趣的部分是:
initobj valuetype [System.Private.CoreLib]System.Nullable`1<int32>
此默认值初始化结构。编译器和运行时都对可空类型进行特殊处理。 default initialized* 结构是 Nullable<>
表示 null
值的方式。
看到这个 SharpLab demo.**
现在如果我们删除属性并继续分配 null
等会怎样?
public static void Main() {
int? j = 3;
Console.WriteLine(j);
j = null;
Console.WriteLine(j);
j = default;
Console.WriteLine(j);
j = new int?();
Console.WriteLine(j);
}
编译器只是将 null
引用“强制转换”到结构类型***。
public static void Main()
{
Nullable<int> num = 3;
Console.WriteLine(num);
Console.WriteLine((Nullable<int>)null);
Console.WriteLine((Nullable<int>)null);
Console.WriteLine((Nullable<int>)null);
}
看到这个SharpLab demo。
我还想指出,调用 new T?()
并不像其他答案之一所暗示的那样进行分配。 Nullable<>
实际上是一个值类型。正如你在上面看到的 new int?()
默认初始化,所以没有分配。
总之都是一样的。这取决于个人喜好。我个人认为使用 null
是最好和最清晰的选择,我认为你很难找到持不同意见的人。根据上下文,我肯定会双重接管 new T?
并可能超过 default
。简单地说:你意味着null
,所以使用null
* 默认初始化不设置 HasValue
或 Value
属性,将它们分别保留为 false
和 default
,为我们提供 null
表示
** 如果您查看上述 Main
方法的 IL,请不要被 box
指令所迷惑。我们正在调用 Console.Writeline
的重载,它接受 object
从而强制对值类型进行装箱。
*** 众所周知,结构实际上不可能是 null
;这同样表明 由运行时和编译器进行特殊处理。