在C#中,结构是根据值进行管理的,对象是引用的。根据我的理解,在创建类的实例时,关键字new
会导致C#使用类信息来创建实例,如下所示:
class MyClass
{
...
}
MyClass mc = new MyClass();
对于struct,您不是创建对象,只是将变量设置为值:
struct MyStruct
{
public string name;
}
MyStruct ms;
//MyStruct ms = new MyStruct();
ms.name = "donkey";
我不明白的是,如果通过MyStruct ms = new MyStruct()
声明变量,这里的关键字new
是什么? 。如果struct不能是一个对象,那么这里new
实例化是什么?
答案 0 :(得分:56)
来自MSDN上的struct (C# Reference)
:
使用new运算符创建struct对象时,会创建它并调用相应的构造函数。与类不同,可以在不使用new运算符的情况下实例化结构。如果不使用new,则字段将保持未分配状态,并且在初始化所有字段之前无法使用该对象。
据我了解,除非确保手动初始化所有字段,否则在不使用 new 的情况下,您实际上无法正确使用结构。如果使用new运算符,构造函数将为您执行此操作。
希望清除它。如果您需要澄清,请告诉我。
修改强>
有很长的评论帖子,所以我想我会在这里添加一些。我认为理解它的最好方法就是试一试。在Visual Studio中创建一个名为“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);
}
}
}
玩弄它。删除构造函数,看看会发生什么。尝试使用只初始化一个变量的构造函数(我已经注释了一个......它不会编译)。尝试使用和不使用 new 关键字(我已经注释掉了一些示例,取消注释并试一试)。
答案 1 :(得分:16)
Catch Eric Lippert's excellent answer from this thread.引用他:
当你“新”一个值类型时,会发生三件事。首先是记忆 经理从短期存储中分配空间。第二, 构造函数传递对短期存储位置的引用。 构造函数运行后,短期内的值 存储位置被复制到值的存储位置, 无论发生在哪里。请记住,值类型存储的变量 实际价值。
(注意,允许编译器优化这三个步骤 如果编译器可以确定这样做永远不会公开一个步骤 部分构造的结构到用户代码。也就是说,编译器可以 生成只是将引用传递给最终存储的代码 位置到构造函数,从而节省一个分配和一个 副本。)
(做出这个答案,因为它确实是一个)
答案 2 :(得分:3)
使用“new MyStuct()”可确保将所有字段设置为某个值。在上面的例子中,没有什么不同。如果不是设置ms.name,而是尝试读取它,那么在VS中会出现“使用可能未分配的字段'名称'”错误。
答案 3 :(得分:3)
任何时候一个对象或结构出现,它的所有领域都会存在;如果这些字段中的任何一个是结构类型,则所有嵌套字段也都存在。当一个数组被创建时,它的所有元素都会存在(并且,如上所述,如果这些元素中的任何一个是结构体,那些结构体的字段也会存在)。所有这些都发生在任何构造函数代码都有机会运行之前。
在.net中,结构构造函数实际上只不过是一个将struct作为'out'参数的方法。在C#中,调用结构构造函数的表达式将分配一个临时的struct实例,在其上调用构造函数,然后使用该临时实例作为表达式的值。请注意,这与vb.net不同,后者生成的构造函数代码将通过清零所有字段开始,但调用者的代码将尝试让构造函数直接在目标上运行。例如:vb.net中的myStruct = new myStructType(whatever)
将在构造函数的第一个语句执行之前清除myStruct
;在构造函数中,对正在构造的对象的任何写入将立即在myStruct
上运行。
答案 4 :(得分:0)
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;
}
测试类的方法生成的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.s i // 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.s i // 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。值类型通常使用OpCodes.Initobj进行初始化,但MSDN也会使用OpCodes.Newobj。
使用newobj创建的 通常。它们通常使用newarr(对于从零开始的一维数组)或作为对象的字段分配为参数或局部变量。分配后,使用Initobj初始化它们。 但是,newobj指令可用于在堆栈上创建值类型的新实例,然后可以作为参数传递,存储在本地,等等。
对于每个值为数字的值类型,从byte
到double
,都有一个已定义的操作码。尽管它们被声明为struct
,但生成的IL存在一些差异,如图所示。
还有两件事需要提及:
ValueType
本身被声明为抽象类
也就是说,你不能直接新它。
struct
s不能包含显式无参数构造函数
也就是说,当您新一个struct
时,您将属于NewMyLong
或NewMyLongWithValue
之上的情况。
总结, new 的值类型和结构是为了语言概念的一致性。
答案 5 :(得分:0)
在结构中,new
关键字不必要地混淆。它没有做任何事情。如果你想使用构造函数,它只是必需的。它不执行new
。
new
的通常含义是分配永久存储(在堆上)。
像C ++这样的语言允许new myObject()
或myObject()
。两者都调用相同的构造函数。但是前者创建了一个新对象并返回一个指针。后者只是创造了一个临时。任何结构或类都可以使用。 new
是一种选择,它意味着什么。
C#没有给你一个选择。类总是在堆中,结构总是在堆栈上。无法在结构上执行真正的new
。经验丰富的C#程序员习惯于此。当他们看到ms = new MyStruct();
他们知道忽略new
只是语法。他们知道它的行为类似于ms = MyStruct()
,它只是分配给现有的对象。
奇怪的是(?),类需要new
。不允许使用c=myClass();
(使用构造函数设置现有对象c
的值。)您必须制作c.init();
之类的内容。所以你真的没有选择 - 构造函数总是为类分配,而从不为结构分配。 new
始终只是装饰。
我假设在结构中要求假new
的原因是你可以轻松地将结构更改为类(假设你在第一次声明时总是使用myStruct=new myStruct();
,这是推荐的。)