结构隐式构造函数与空构造函数
本文关键字:构造函数 结构 | 更新日期: 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);
我想后面看起来更"美容"正确的