关键字“new”对C#中的结构做了什么?

在C#中,Structs是按照值来pipe理的,对象是在引用中。 根据我的理解,当创build一个类的实例时,关键字new导致C#使用类信息来创build实例,如下所示:

 class MyClass { ... } MyClass mc = new MyClass(); 

对于结构,你不是创build一个对象,而是简单地将一个variables设置为一个值:

 struct MyStruct { public string name; } MyStruct ms; //MyStruct ms = new MyStruct(); ms.name = "donkey"; 

我不明白的是,如果通过MyStruct ms = new MyStruct()声明variables,那么关键字new在这里对语句做了什么? 。 如果struct不能成为一个对象,那么这里实例化的new东西是什么?

从MSDN上的struct (C# Reference)

当你使用new运算符创build一个struct对象时,它会被创build,并调用相应的构造函数。 与类不同,可以在不使用新运算符的情况下实例化结构。 如果您不使用新的字段将保持未分配状态,并且在所有字段初始化之前对象不能使用。

根据我的理解,除非确保手动初始化所​​有字段,否则实际上无法正确使用结构而不使用的结构。 如果你使用new运算符,构造函数会为你做这个。

希望清除它。 如果你需要澄清这个让我知道。


编辑

有相当长的评论线程,所以我想我会在这里添加更多。 我认为理解它的最好方法就是放弃它。 在Visual Studio中创build一个名为“StructTest”的控制台项目,并将以下代码复制到其中。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace struct_test { class Program { public struct Point { public int x, y; public Point(int x) { this.x = x; this.y = 5; } public Point(int x, int y) { this.x = x; this.y = y; } // It will break with this constructor. If uncommenting this one // comment out the other one with only one integer, otherwise it // will fail because you are overloading with duplicate parameter // types, rather than what I'm trying to demonstrate. /*public Point(int y) { this.y = y; }*/ } static void Main(string[] args) { // Declare an object: Point myPoint; //Point myPoint = new Point(10, 20); //Point myPoint = new Point(15); //Point myPoint = new Point(); // Initialize: // Try not using any constructor but comment out one of these // and see what happens. (It should fail when you compile it) myPoint.x = 10; myPoint.y = 20; // Display results: Console.WriteLine("My Point:"); Console.WriteLine("x = {0}, y = {1}", myPoint.x, myPoint.y); Console.ReadKey(true); } } } 

玩它。 删除构造函数,看看会发生什么。 尝试使用只初始化一个variables的构造函数(我已经评论了一个…它不会编译)。 尝试使用和不使用新的关键字(我已经评论了一些例子,取消注释,并给他们一个尝试)。

赶上埃里克·利波特从这个线程的优秀答案。 引用他的话:

当你“新”一个值types时,发生三件事情。 首先,内存pipe理器从短期存储中分配空间。 其次,构造函数传递一个对短期存储位置的引用。 在构造函数运行后,短期存储位置中的值被复制到值的存储位置,无论哪里发生。 请记住,值types的variables存储实际值。

(请注意,如果编译器可以确定这样做不会将部分构造的结构暴露给用户代码,那么编译器可以将这三个步骤优化为一个步骤,也就是说,编译器可以生成简单地将引用传递给最终存储位置到构造函数,从而节省一个分配和一个副本。)

做出这个答案,因为它确实是一个

使用“新的MyStuct()”确保所有字段都设置为某个值。 在上面的情况下,没有什么不同。 如果不是在设置ms.name的地方尝试读取它,你会得到一个“使用可能的未分配的字段名称”错误在VS.

任何时候一个对象或结构的出现,它的所有领域也都存在; 如果这些字段中的任何一个是结构types,那么所有的嵌套字段也会存在。 当一个数组被创build时,它的所有元素都会生成(如上所述,如果这些元素中的任何一个是结构体,那么这些结构体的字段也会存在)。 所有这些都在任何构造函数代码有机会运行之前发生。

在.net中,struct构造函数实际上只不过是一个将结构作为“out”参数的方法。 在C#中,调用结构构造函数的expression式将分配一个临时结构实例,调用构造函数,然后使用该临时实例作为expression式的值。 请注意,这不同于vb.net,其中构造函数的生成代码将通过清零所有字段开始,但调用者的代码将尝试让构造函数直接在目标上运行。 例如:在构造函数的第一个语句执行之前,vb.net中的myStruct = new myStructType(whatever)将清除myStruct ; 在构造函数中,对正在构build的对象的任何写操作都将立即在myStruct进行操作。

ValueType和结构在C#中是特殊的。 在这里,我向你展示了当你新的东西时会发生什么。

这里我们有以下几点

  •  partial class TestClass { public static void NewLong() { var i=new long(); } public static void NewMyLong() { var i=new MyLong(); } public static void NewMyLongWithValue() { var i=new MyLong(1234); } public static void NewThatLong() { var i=new ThatLong(); } } [StructLayout(LayoutKind.Sequential)] public partial struct MyLong { const int bits=8*sizeof(int); public static implicit operator int(MyLong x) { return (int)x.m_Low; } public static implicit operator long(MyLong x) { long y=x.m_Hi; return (y<<bits)|x.m_Low; } public static implicit operator MyLong(long x) { var y=default(MyLong); y.m_Low=(uint)x; y.m_Hi=(int)(x>>bits); return y; } public MyLong(long x) { this=x; } uint m_Low; int m_Hi; } public partial class ThatLong { const int bits=8*sizeof(int); public static implicit operator int(ThatLong x) { return (int)x.m_Low; } public static implicit operator long(ThatLong x) { long y=x.m_Hi; return (y<<bits)|x.m_Low; } public static implicit operator ThatLong(long x) { return new ThatLong(x); } public ThatLong(long x) { this.m_Low=(uint)x; this.m_Hi=(int)(x>>bits); } public ThatLong() { int i=0; var b=i is ValueType; } uint m_Low; int m_Hi; } 

并且testing类方法的生成的IL将是

  • IL

     // NewLong .method public hidebysig static void NewLong () cil managed { .maxstack 1 .locals init ( [0] int64 i ) IL_0000: nop IL_0001: ldc.i4.0 // push 0 as int IL_0002: conv.i8 // convert the pushed value to long IL_0003: stloc.0 // pop it to the first local variable, that is, i IL_0004: ret } // NewMyLong .method public hidebysig static void NewMyLong () cil managed { .maxstack 1 .locals init ( [0] valuetype MyLong i ) IL_0000: nop IL_0001: ldloca.si // push address of i IL_0003: initobj MyLong // pop address of i and initialze as MyLong IL_0009: ret } // NewMyLongWithValue .method public hidebysig static void NewMyLongWithValue () cil managed { .maxstack 2 .locals init ( [0] valuetype MyLong i ) IL_0000: nop IL_0001: ldloca.si // push address of i IL_0003: ldc.i4 1234 // push 1234 as int IL_0008: conv.i8 // convert the pushed value to long // call the constructor IL_0009: call instance void MyLong::.ctor(int64) IL_000e: nop IL_000f: ret } // NewThatLong .method public hidebysig static void NewThatLong () cil managed { // Method begins at RVA 0x33c8 // Code size 8 (0x8) .maxstack 1 .locals init ( [0] class ThatLong i ) IL_0000: nop // new by calling the constructor and push it's reference IL_0001: newobj instance void ThatLong::.ctor() // pop it to the first local variable, that is, i IL_0006: stloc.0 IL_0007: ret } 

这些方法的行为在IL代码中进行了评论。 你可能想看看OpCodes.Initobj和OpCodes.Newobj 。 值types通常使用OpCodes.Initobj进行初始化,但是如MSDN所示, OpCodes.Newobj也将被使用。

  • OpCodes.Newobj中的描述

    值types通常不是使用newobj创build的。 它们通常被分配为参数或局部variables,使用newarr(对于基于零的一维数组),或作为对象的字段。 一旦分配,它们就使用Initobj进行初始化。 但是 ,newobj指令可以用来在堆栈上创build一个新值types的实例,然后可以将其作为parameter passing,存储在本地等等。

对于每个从数字到数字的值types,都有一个定义的操作码。 尽pipe它们被声明为struct ,但是在生成的IL中却有一些不同,如图所示。

这里还有两件事要提到:

  1. ValueType本身被声明为抽象类

    也就是说,你不能直接新的

  2. struct s不能包含显式的无参数构造函数

    也就是说,当你新build一个struct ,你会NewMyLongNewMyLongWithValue

总而言之 ,价值types和结构的新颖性是为了语言概念的一致性。

在一个结构中, new关键字是不必要的混淆。 它什么都不做。 这只是如果你想使用构造函数。 它不执行一个new

new的通常意义是分配永久存储(在堆上)。像C ++这样的语言允许new myObject()或者myObject() 。 两者都调用相同的构造函数。 但前者创build一个新的对象并返回一个指针。 后者只是创造一个温度。 任何结构或类都可以使用。 new是一个select,它意味着什么。

C#不会给你一个select。 类总是在堆中,结构总是在堆栈上。 在一个结构体上执行一个真正的new是不可能的。 有经验的C#程序员习惯了这一点。 当他们看到ms = new MyStruct(); 他们知道忽略new只是语法。 他们知道它的行为像ms = MyStruct() ,它只是分配给一个现有的对象。

奇怪(?),类需要newc=myClass(); 是不允许的(使用构造函数来设置现有对象c值)。你必须做类似c.init(); 。 所以你真的从来没有select – 构造函数总是分配给类,而不是为结构分配。 new的永远只是装饰。

我假设在结构中要求假new的原因是你可以很容易地将一个结构变成一个类(假设你总是使用myStruct=new myStruct();当你第一次声明的时候,这是推荐的)。