关键字“new”对C#中的结构有什么作用?

时间:2012-02-09 08:27:03

标签: c# object struct

在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实例化是什么?

6 个答案:

答案 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.InitobjOpCodes.Newobj。值类型通常使用OpCodes.Initobj进行初始化,但MSDN也会使用OpCodes.Newobj

  • OpCodes.Newobj

    中的说明
      

    使用newobj创建的 通常。它们通常使用newarr(对于从零开始的一维数组)或作为对象的字段分配为参数或局部变量。分配后,使用Initobj初始化它们。 但是,newobj指令可用于在堆栈上创建值类型的新实例,然后可以作为参数传递,存储在本地,等等。

对于每个值为数字的值类型,从bytedouble,都有一个已定义的操作码。尽管它们被声明为struct,但生成的IL存在一些差异,如图所示。

还有两件事需要提及:

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

    也就是说,你不能直接它。

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

    也就是说,当您一个struct时,您将属于NewMyLongNewMyLongWithValue之上的情况。

  3. 总结 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();,这是推荐的。)