A a = new A();
A a = null;
A a;
这三行代码如何涉及内存?
我知道第一行将在堆中创建一个内存,但是后面两行呢?
如果 A a 是一个字段和局部变量,它会如何工作?
A a = new A();
A a = null;
A a;
这三行代码如何涉及内存?
我知道第一行将在堆中创建一个内存,但是后面两行呢?
如果 A a 是一个字段和局部变量,它会如何工作?
A
的新实例并将其赋值给变量a
。a
赋为null
。如果不使用a
,编译器可能会将其优化掉。A a = default(A);
,和2一样,因为default(A)
是null
。对于方法变量,如果没有分配,则会显示警告或错误。如果未使用,则此选项也可以进行优化。null
(它知道它是什么:它没有值)。 - Patrick HofmanA a
是一个字段,例如:field。 class MyClass {
...
// A a is field of some class/structure
A a = new A(); // A a = null; or A a;
...
}
A a = new A();
A
类型的字段 a
具有新实例化的 A
对象作为初始值; 这两行代码是 相等的 (类型为 A
的字段 a
具有初始值 null
):
A a = null;
A a;
自从
“无论是静态字段还是实例字段,其初始值都是默认值。”
https://msdn.microsoft.com/en-us/library/aa645756(v=vs.71).aspx
对于局部变量,例如
public void MyMethod() {
...
// A a is a local variable in some method
A a = new A(); // A a = null; or A a;
...
}
https://msdn.microsoft.com/en-us/library/4y7h161d(v=vs.71).aspx
所以。 A a = new A(); // "a" of type "A" declaration with new instance of A as an initial value
A a = null; // "a" of type "A" declaration with null initial value
A a; // just "a" declaration, "a" contains trash and should be initialized before using
假设A
是一个引用类型并且这段代码在方法中:
A a = new A();
总是创建一个新的对象在堆上,并将一个指向该新对象的引用赋值给 a
。
A a = null;
和 A a;
都会将 null
赋值给 a
。
然而,对于 A a = null;
和 A a;
生成的 IL 可能会有所不同。
考虑以下简单的程序:
static void Main()
{
string s;
if (Environment.TickCount > 0)
s = "A";
else
s = "B";
Console.WriteLine(s);
}
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 2
.locals init (
[0] string s)
L_0000: call int32 [mscorlib]System.Environment::get_TickCount()
L_0005: ldc.i4.0
L_0006: ble.s L_0010
L_0008: ldstr "A"
L_000d: stloc.0
L_000e: br.s L_0016
L_0010: ldstr "B"
L_0015: stloc.0
L_0016: ldloc.0
L_0017: call void [mscorlib]System.Console::WriteLine(string)
L_001c: ret
}
static void Main()
{
string s = null;
if (Environment.TickCount > 0)
s = "A";
else
s = "B";
Console.WriteLine(s);
}
而IL更改为:
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 2
.locals init (
[0] string s)
L_0000: ldnull <====== Lookie here
L_0001: stloc.0 <====== and here
L_0002: call int32 [mscorlib]System.Environment::get_TickCount()
L_0007: ldc.i4.0
L_0008: ble.s L_0012
L_000a: ldstr "A"
L_000f: stloc.0
L_0010: br.s L_0018
L_0012: ldstr "B"
L_0017: stloc.0
L_0018: ldloc.0
L_0019: call void [mscorlib]System.Console::WriteLine(string)
L_001e: ret
.locals init ([0] string s)
已将其初始化为null)。string
以简化问题,但如果您使用自己的类,情况也是一样的。)A a = new A();
这实际上是实例化了一个类型为A的新对象。a是对该对象的引用。a存储在堆栈上,而实际的对象存储在堆上。
A a = null;
只是在堆栈上创建了引用 - 没有数据存储在堆上。
A a;
我认为这与A a = null;
相同 - 编辑 需要从OP获取问题背景的澄清。
A a
与 A a = null
是相同的。在代码块中,你不能使用 a.Anything
,因为它会在编译时显示“未初始化的变量”,但如果你将其赋值为 null 并以相同的方式使用它,则会抛出 NullReferenceException 异常。这是唯一的原因。 - Suren Srapyan所有三个结构都为局部作用域变量a在堆栈上分配了一个引用。
A a = new A();
在堆上构造一个对象(假设A是一个类,因为它的引用可以被赋值为空)。如果您的团队指南支持显式类型声明而不是var
,则此结构很有用。否则,在这种情况下,您将使用var
,并且类型由编译器推断:
var a = new A();
A a = null;
将null引用分配给a,如果您要引入对引用的读取访问,则此方法非常有用。例如:
Func<int, int> factorial = null;
factorial = n => n < 3 ? n : n * factorial(n-1);
lambda表达式的作用域继承变量声明的作用域,因此需要进行变量初始化。
如果没有= null
,则会被视为编译器错误,因为它引入了对委托的读取访问,而您还没有非空值,也不想虚构一个。
这种结构很有用,因为您无法推断出null的类型。
您可以使用var a = default(A);
代替,这对于值类型也适用。
A a
;只是声明一个变量。如果需要引入对变量的读取访问,则必须在作用域中分配它。基本上您无法使用它,所以没有理由以这种方式声明它。一般规则是将变量声明更接近第一次使用。
这种结构在您的代码中出现的有效用例仅限于自动重构(ReSharper)期间,当使用拆分声明和赋值
重构时,它会将var
转换为A
,紧接着是一个移动到外部作用域
重构。
假设您有一个类型为SomeVerylongTypeName<EvenMoreLongTypeName>
的变量x,它是一个推断类型,并且想要获取类型名称。
var x = container.GetFirst(); // some imaginary GetFirst method which returns an instance of non-keyboard friendly type.
SomeVerylongTypeName<EvenMoreLongTypeName> a;
a = x;
第二和第三个语句是相同的,即分配一个指向“null”的空引用; 第一个语句将在托管堆中分配A类的新实例,并将其地址分配给引用变量
A a = null; A b = a;
不会导致编译错误,但是 A a; A b = a;
会导致编译错误。 - Matthew Watson
A
是一个类还是结构体? - Jon Skeet