Struct隐式vs空构造函数

时间:2012-02-22 11:48:25

标签: c#

考虑以下结构:

    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#

3 个答案:

答案 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);

我想后者看起来更“美观”正确