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


69

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


3
的实例struct 一个对象。您可能会误解的区别是值类型和引用类型之间的区别。
Ed S.

但是在C语言中没有对象,而struct不是对象。那么在C#中struct是作为对象实现的吗?
KMC 2012年

1
从C角度考虑C#并没有帮助。忽略语法上的差异,它们是完全不同的语言。
Ed S.

1
@KMC即使在C中也有一个对象。您会误解“对象”的含义–是可以理解的,因为在不同的上下文中它意味着许多不同的事物。例如,在C ++中(我认为C很相似),它只是内存中的空间:驻留在内存中的所有对象都是对象。
Konrad Rudolph

相关答案:stackoverflow.com/a/3943596/380384。不使用初始化structnew
约翰·阿列克谢

Answers:


62

struct (C# Reference)MSDN上:

使用new运算符创建结构对象时,将创建该对象并调用适当的构造函数。与类不同,可以在不使用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);
        }
    }
}

玩吧。删除构造函数,看看会发生什么。尝试使用仅初始化一个变量的构造函数(我已经注释掉了一个……它将无法编译)。尝试使用和不使用关键字(我已经注释掉了一些示例,取消注释并尝试一下)。


“可以实例化结构”?但是struct不能是对象,不是吗?结构中的“字段”是属性和方法吗?如果struct不是对象,为什么需要初始化其字段?我想我需要进一步澄清。谢谢。
KMC 2012年

为什么不会需要结构的字段被初始化,如果不调用构造函数?如果不初始化它们,并且不调用构造函数对其进行初始化,则它们将保持未初始化状态。

结构是各种对象。在Microsoft网站上被描述为“轻量级对象”。它可以有变量,但不能有函数。一个结构有点像一个类,但是所有成员都是公共的,您不能有任何功能。它允许您存储信息,但不能像在类中那样操作或控制该信息。您可以使“新”结构使用相同的变量,但清除所有数据。
joshhendo 2012年

7
@joshhendo:嗯?结构的实例是一个对象,它们当然可以包含方法和私有字段。您在这里使初学者感到困惑。
Ed S.

该术语可能有点混乱。结构最初是用C语言编写的,这不是一种面向对象的语言。它们仍然存在于C ++和其他类似C的语言(C#是。)中,最好将结构视为“记录”或分组在一起的一组相关变量。这只是C#结构的条件,在使用该结构之前,需要初始化该结构内的所有变量。您可以手动执行此操作,也可以使用new运算符。最好将结构视为“轻量级对象”。
joshhendo 2012年

20

从该主题中抓住Eric Lippert的出色答案。引用他的话:

当您“新建”一个值类型时,会发生三件事。首先,内存管理器从短期存储中分配空间。其次,向构造函数传递对短期存储位置的引用。构造函数运行后,将短期存储位置中的值复制到该值的存储位置(无论发生在何处)。请记住,值类型的变量存储实际值。

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

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


值得注意的是,由于out参数是C#概念,而不是.NET运行时所使用的概念,因此将部分构造的结构作为out参数传递给外部方法会将其值暴露给外部代码,即使C#编译器假定不会的。例如,可以定义一种结构,以便myThing = newmyThing(5);初始化一个字段,myThing而其他字段不受影响。
2013年

3
我认为不同的语言群体可能对.NET应该有自己的看法,并假装它适合他们的看法。例如,C#组可能认为.NET应该具有可强制执行的out参数,如果每个人都使用C#进行编程,则带有out语言参数的虚拟方法将被其他语言视为具有ref参数的虚拟方法。在某些情况下,语言不仅限于其他语言实现者可能想要实现的功能的最小子集,这很好,但是也存在危险。
超级猫

1
这应该是答案
周一

3

使用“ new MyStuct()”可确保将所有字段设置为某个值。在上述情况下,没有什么不同。如果未在尝试读取的位置设置ms.name,而是在VS中收到“使用可能的未分配字段'name'”错误。


3

每当对象或结构存在时,其所有字段也都存在。如果这些字段中的任何一个是struct类型,则所有嵌套字段也都存在。创建数组时,其所有元素都会存在(并且,如上所述,如果这些元素中的任何一个是结构,则这些结构的字段也将存在)。所有这些发生在任何构造函数代码有机会运行之前。

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


2

在结构中,new关键字不必要地造成混淆。它什么也没做。如果要使用构造函数,则仅需要它。它并没有执行new

通常的含义new是(在堆上)分配永久性存储。像C ++这样的语言允许new myObject()或只是myObject()。两者都调用相同的构造函数。但是前者创建一个新对象并返回一个指针。后者仅创建一个临时文件。任何结构或类都可以使用。new是一种选择,它意味着某些东西。

C#没有给您选择。类总是在堆中,而结构总是在栈中。无法new在结构上执行实数。经验丰富的C#程序员已经习惯了这一点。当他们看到时,ms = new MyStruct();便知道忽略了newas as语法。他们知道这就像ms = MyStruct(),只是分配给现有对象。

奇怪的是,类需要使用newc=myClass();不允许(使用构造函数设置现有对象的值c。)您必须进行类似的操作c.init();。因此,您真的别无选择-构造函数总是分配类,而不分配结构。的new永远只是摆设。

我假设要求new在结构中使用false的原因是,因此您可以轻松地将结构更改为类(假设myStruct=new myStruct();在第一次声明时总是使用该结构,建议使用。)


1
考虑到实现细节是错误的,结构并不总是分配在堆栈上,例如,类上的结构字段,装箱等等……而new实际上可以做一些事情!
Eyal Solnik '16

1
令人困惑的是,人们将这个问题理解为“为什么我应该对结构使用new”。但是,如果您阅读了该书,那么问题实际上是最后一个短语“这里的新实例化了什么?” 这是一个关于他们为什么选择有趣的语法的问题。
Owen Reynolds

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将是

  • 白介素

    // 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. structs不能包含显式的无参数构造函数

    也就是说,当您新增a时struct,您将陷入NewMyLongor或以上的情况NewMyLongWithValue

总而言之的值类型和结构是为了保证语言概念的一致性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.