考虑以下结构:
struct S
{
public string s;
}
1:
之间有什么区别? S instance = new S();
instance.s = "foo";
和2:
S instance;
instance.s = "foo";
两个版本编译并运行正常
我很想知道幕后发生了什么。
编辑:
我想在2 S被取消分配之前,我们在其s字段上放置一个值;
因为这不起作用:
S instance;
if (inst.s == null)
inst.s = "foo"; //Compiler drops : Use of possibly unassigned field 's'
虽然这样做:
S instance;
inst.s = "foo";
if (inst.s == null)
inst.s = "bar"; //Compiler drops : Use of possibly unassigned field 's'
这也有效:
S inst = new S();
if (inst.s == null)
inst.s = "foo";
我欢迎有关此行为的任何更深层次的解释
更新
我找到了这两个帖子,完成了Marc的回答:
why are mutable structs evil
when to use struct in c#
答案 0 :(得分:5)
之间有什么区别
S instance = new S();
instance.s = "foo";
和
S instance;
instance.s = "foo";
正如马克正确指出的那样,两者都同样糟糕;正确的做法是创建一个在构造函数中接受字符串的不可变结构。
正如Marc正确指出的那样,功能上没有区别。
然而,这并没有回答你实际问过的问题,即“幕后会发生什么?”通过“幕后”,我假设您正在讨论编译器对代码的语义分析,如C#规范中所述。
幸运的是,这两种情况之间的区别非常明确。
首先,正如您所正确注意的那样,在第一种情况下,变量被认为是在第一个语句之后明确赋值。在第二种情况下,直到第二个语句之后才认为变量是明确赋值的。
然而,明确的赋值分析只是代码实际含义的结果,如下所示。
第一个片段:
instance
instance
instance
是明确分配的,因为它的所有位都是从另一个值复制的instance
第二个片段
instance
instance
instance
已明确分配,因为其所有字段均已分配编译器允许注意到无法确定是否创建,初始化和复制了临时文件。如果它确定那么允许忽略临时的创建,并为两个片段生成相同的代码。编译器不是必需这样做;这是优化,永远不需要优化。
现在,您可能想知道在什么情况下可以确定临时创建,初始化和复制。如果您确实想知道那么请阅读我关于这个主题的文章:
http://blogs.msdn.com/b/ericlippert/archive/2010/10/11/debunking-another-myth-about-value-types.aspx
答案 1 :(得分:4)
功能上没什么。请注意没有显式分配,S instance
(没有new()
)不是“明确分配”,但是:因为(在原始问题中)你是(是)分配字段,这无关紧要。如果出现以下情况,C#中的结构必须分配:
new()
)实际上,除非你知道完全你正在做什么(以及为什么),否则可变结构是一个非常糟糕的主意。此外,公共领域通常是一个坏主意两个。混合两个糟糕的想法,为了好玩; p
如果你真的想在这里使用struct
,我的版本将是:
S instance = new S("foo");
使用:
struct S {
private readonly string value;
public string Value { get { return value; } }
public S(string value) { this.value = value; }
public override string ToString() { return value ?? ""; }
public override int GetHashCode() {return value==null?0:value.GetHashCode();}
public override bool Equals(object obj) { return value == ((S) obj).value; }
}
答案 2 :(得分:1)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
struct S { public string s; }
static void Main(string[] args)
{
S instance = new S();
instance.s = "foo";
S instance1;
instance1.s = "foo";
}
}
}
c:> ildasm c:\ Blyme \ ConsoleApplication1 \ bin \ Debug \ ConsoleApplication1.exe
给我以下msil代码(在Main方法上)
.method private hidebysig static void 'Main'(string[] 'args') cil managed
{
.entrypoint
// Code size 34 (0x22)
.maxstack 2
.locals init (valuetype 'ConsoleApplication1'.'Program'/'S' V_0,
valuetype 'ConsoleApplication1'.'Program'/'S' V_1)
IL_0000: nop
IL_0001: ldloca.s V_0
IL_0003: initobj 'ConsoleApplication1'.'Program'/'S'
IL_0009: ldloca.s V_0
IL_000b: ldstr "foo"
IL_0010: stfld string 'ConsoleApplication1'.'Program'/'S'::'s'
IL_0015: ldloca.s V_1
IL_0017: ldstr "foo"
IL_001c: stfld string 'ConsoleApplication1'.'Program'/'S'::'s'
IL_0021: ret
} // end of method 'Program'::'Main'
堆栈分配没有区别(只有2个),只是在initobj上有一个额外的调用。情况2意味着不需要initobj,并且它确实有意义,因为这是值类型。
你会说吗?int a = 0; 要么 int a = new int(0);
我想后者看起来更“美观”正确