结构隐式构造函数与空构造函数

本文关键字:构造函数 结构 | 更新日期: 2023-09-27 18:25:36

考虑以下结构:

    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的回答:
为什么可变结构是邪恶的
何时在c#

结构隐式构造函数与空构造函数

中使用结构

有什么区别

S instance = new S();     
instance.s = "foo"; 

S instance;     
instance.s = "foo";

正如马克正确指出的那样,两者都同样糟糕;正确的做法是创建一个不可变的结构,它在构造函数中接受字符串。

正如马克正确指出的那样,功能上没有区别。

然而,这并不能回答你实际提出的问题,即";幕后发生了什么"通过";"幕后";我假设您谈论的是编译器对代码的语义分析,如C#规范中所述。

幸运的是,规范非常清楚这两种情况之间的区别。

首先,正如您正确指出的,在第一种情况下,变量被认为是在第一个语句之后明确分配的。在第二种情况下,直到第二个语句之后,变量才被认为是明确赋值的。

然而,确定性赋值分析只是代码实际含义的结果,如下所示。

第一个片段:

  • instance分配存储
  • 为临时值分配临时存储
  • 将临时值初始化为默认结构状态
  • 将临时值的位复制到instance的存储器
  • 现在instance肯定被赋值了,因为它的所有位都是从另一个值复制的
  • 将字符串引用复制到instance的存储器中

第二个片段

  • instance分配存储
  • 将字符串引用复制到instance的存储器中
  • 现在instance被明确分配了,因为它的所有字段都被分配了

允许编译器注意到,无法确定是否创建、初始化和复制了临时文件。如果它确实确定了这一点,那么允许取消临时的创建,并为两个片段生成相同的代码。编译器不是所必需的;这是一个优化,从不需要优化。

现在,您可能想知道在什么情况下可以确定临时文件已创建、初始化和复制。如果你真的想知道,那就阅读我关于的文章

https://ericlippert.com/2010/10/11/debunking-another-myth-about-value-types/

功能上什么都没有。请注意,如果没有显式赋值,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; }
}
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代码(在主方法上)

.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);

我想后面看起来更"美容"正确的