.NET中的struct和class有什么区别?

.NET中的struct和class有什么区别?

我正在寻找一个清晰,简洁和准确的答案。 作为实际答案,理想情况下,尽pipe欢迎提供良好的解释。

在.NET中,有两类types, 引用types值types

结构是值types ,类是引用types

一般的区别在于引用types存在于堆中,并且值types是内联的,也就是说,无论在哪里定义variables或字段。

包含值types的variables包含整个值types值。 对于结构来说,这意味着variables包含整个结构及其所有字段。

包含一个引用types的variables包含一个指针,或者是指向实际值所在的内存中的其他地方的引用

这有一个好处,首先:

  • 值types始终包含一个值
  • 引用types可以包含一个 引用( null- reference),这意味着它们此刻不会引用任何内容

在内部, 引用types被实现为指针,并且知道variables赋值如何工作,还有其他的行为模式:

  • 值typesvariables的内容复制到另一个variables中,将整个内容复制到新variables中,使两者不同。 换句话说,复制后,更改为一个不会影响另一个
  • 引用typesvariables的内容复制到另一个variables中,复制引用,这意味着您现在有两个对实际数据的相同存储位置的引用。 换句话说,复制之后,更改一个引用中的数据也会影响另一个引用,但仅仅是因为您只是在两个地方查看相同的数据

当你声明variables或字段时,下面介绍两种types的区别:

  • variables: 值types位于堆栈上, 引用types位于堆栈上,作为指向实际内存所在的堆内存的指针(尽pipe请注意Eric Lipperts文章系列:堆栈是实现细节 。
  • class / struct-field: valuetypes完全存在于types中, 引用types作为一个指向实际内存所在的堆内存的指针存在于types中。

每个简短的摘要:

类仅:

  • 可以支持inheritance
  • 是引用(指针)types
  • 引用可以为null
  • 每个新实例都有内存开销

仅结构:

  • 不支持inheritance
  • 是价值types
  • 通过值传递(如整数)
  • 不能有空引用(除非使用Nullable)
  • 每个新实例没有内存开销 – 除非“装箱”

类和结构:

  • 复合数据types通常用于包含一些具有某种逻辑关系的variables
  • 可以包含方法和事件
  • 可以支持接口

在.NET中,结构和类声明区分了引用types和值types。

当你传递一个引用types时,只有一个实际存储。 所有访问实例的代码都访问同一个实例。

当你传递一个值types时,每一个都是一个副本。 所有的代码都在自己的副本上工作。

这可以用一个例子来显示:

struct MyStruct { string MyProperty { get; set; } } void ChangeMyStruct(MyStruct input) { input.MyProperty = "new value"; } ... // Create value type MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; ChangeMyStruct(testStruct); // Value of testStruct.MyProperty is still "initial value" // - the method changed a new copy of the structure. 

对于一个class级,这将是不同的

 class MyClass { string MyProperty { get; set; } } void ChangeMyClass(MyClass input) { input.MyProperty = "new value"; } ... // Create reference type MyClass testClass = new MyClass { MyProperty = "initial value" }; ChangeMyClass(testClass); // Value of testClass.MyProperty is now "new value" // - the method changed the instance passed. 

类可以是什么 – 参考可以指向一个null。

结构是实际值 – 它们可以是空的,但不能为空。 由于这个原因,结构体总是有一个没有参数的默认构造函数 – 它们需要一个“起始值”。

类的实例存储在托pipe堆上。 所有包含实例的variables只是对堆上实例的引用。 将对象传递给方法会导致传递的引用的副本,而不是对象本身。

结构(技术上,值types)存储在任何地方,很像一个原始types。 内容可以随时由运行时复制,而不需要调用自定义的拷贝构造函数。 将值types传递给方法涉及复制整个值,而不用调用任何可定制的代码。

C ++ / CLI名称使得区别变得更好:“ref class”是一个首先描述的类,“value class”是其次描述的一个类。 C#使用的关键字“class”和“struct”只是一些必须学习的东西。

除了其他答案中描述的所有差异外:

  1. 结构不能有一个明确的无参数构造函数,而类可以
  2. 结构不能有析构函数,而类可以
  3. 结构不能从另一个类inheritance,而一个类可以。 结构和类都可以从接口inheritance。

如果您是在解释所有差异的video之后,可以查看第29部分 – C#教程 – C#中的类和结构之间的区别

从微软的select类和结构 …

作为一个经验法则,框架中的大多数types应该是类。 然而,在某些情况下,值types的特征使它更适合使用结构。

考虑一个结构而不是一个类:

  • 如果这种types的实例很小,通常是短暂的,或者通常embedded其他对象中。

X AVOID结构,除非types具有以下所有特征:

  • 它在逻辑上表示一个单一的值,类似于原始types(int,double等)。
  • 它有一个16字节以下的实例大小。
  • 它是不可改变的。 (不能改变)
  • 它不会经常被装箱。

结构与类

结构是一个值types,所以它被存储在堆栈中,但是一个类是一个引用types并存储在堆上。

一个结构不支持inheritance和多态,但一个类同时支持。

默认情况下,所有结构成员都是公共的,但是类成员默认情况下是私有的。

由于结构是一个值types,所以我们不能将null指定给一个结构对象,但对于一个类来说则不是这样。

那么,对于初学者来说,结构是通过值而不是通过引用传递的。 结构对于相对简单的数据结构是有好处的,而从架构的angular度来看,类通过多态和inheritance具有更多的灵活性。

其他人可能会给我比我更多的细节,但是当我要去的结构很简单的时候,我使用结构。

除了访问说明符的基本区别之外,还有上面提到的几点,我想补充一些主要的区别,其中包括上面提到的很less的代码样例和输出,这会给参考和值的一个更清晰的概念

结构:

  • 是值types,不需要堆分配。
  • 内存分配是不同的,并存储在堆栈中
  • 对于小数据结构很有用
  • 影响性能,当我们将值传递给方法时,我们传递整个数据结构,并将其全部传递给堆栈。
  • 构造函数只是返回结构体本身(通常在栈上的临时位置),然后根据需要复制该值
  • 每个variables都有它们自己的数据副本,而且一个variables不能影响另一个variables。
  • 不支持用户指定的inheritance,并且它们隐式地inheritance了types对象

类:

  • 引用types值
  • 存储在堆中
  • 存储对dynamic分配的对象的引用
  • 用new运算符调用构造函数,但不会在堆上分配内存
  • 多个variables可能对同一个对象有一个引用
  • 对一个variables的操作可能会影响另一个variables引用的对象

代码示例

  static void Main(string[] args) { //Struct myStruct objStruct = new myStruct(); objStruct.x = 10; Console.WriteLine("Initial value of Struct Object is: " + objStruct.x); Console.WriteLine(); methodStruct(objStruct); Console.WriteLine(); Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x); Console.WriteLine(); //Class myClass objClass = new myClass(10); Console.WriteLine("Initial value of Class Object is: " + objClass.x); Console.WriteLine(); methodClass(objClass); Console.WriteLine(); Console.WriteLine("After Method call value of Class Object is: " + objClass.x); Console.Read(); } static void methodStruct(myStruct newStruct) { newStruct.x = 20; Console.WriteLine("Inside Struct Method"); Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x); } static void methodClass(myClass newClass) { newClass.x = 20; Console.WriteLine("Inside Class Method"); Console.WriteLine("Inside Method value of Class Object is: " + newClass.x); } public struct myStruct { public int x; public myStruct(int xCons) { this.x = xCons; } } public class myClass { public int x; public myClass(int xCons) { this.x = xCons; } } 

产量

Struct对象的初始值是:10

里面的Struct方法内部Struct对象的方法值是:20

Struct对象的Method调用值为10之后

Class对象的初始值是:10

内部类方法里面类方法的值对象是:20

Class对象的Method调用值为:20

在这里,您可以清楚地看到按价值调用和按参考调用之间的差异。

只是为了使它完成,使用Equals方法有另外一个区别,它被所有的类和结构所inheritance。

假设我们有一个阶级和一个结构:

 class A{ public int a, b; } struct B{ public int a, b; } 

在Main方法中,我们有4个对象。

 static void Main{ A c1 = new A(), c2 = new A(); c1.a = c1.b = c2.a = c2.b = 1; B s1 = new B(), s2 = new B(); s1.a = s1.b = s2.a = s2.b = 1; } 

然后:

 s1.Equals(s2) // true s1.Equals(c1) // false c1.Equals(c2) // false c1 == c2 // false 

所以 ,结构适合类似数字的对象,比如点(保存x和y坐标)。 class级适合其他人。 即使2人有名字,身高,体重……他们仍然是2人。

结构是实际值 – 它们可以是空的,但不能为空

这是真的,但是也要注意,从.NET 2的结构支持Nullable版本,C#提供了一些语法糖,使它更容易使用。

 int? value = null; value = 1; 

如前所述:类是引用types,而结构是具有所有后果的值types。

作为规则的大拇指,框架devise指南build议在下列情况下使用Structs而不是类:

  • 它有一个16字节以下的实例大小
  • 它在逻辑上表示一个单一的值,类似于原始types(int,double等)
  • 它是不可改变的
  • 它不会经常被装箱

为了增加其他答案,有一个值得注意的基本差异,那就是它如何存储在内存中。 结构是值types,所以它们存储一个值,类是引用types,所以它们引用一个类。

  • 在存储数据的包含类中分配结构内存。
  • 对于一个类来说,包含类将只包含一个指向不同内存区域的新类的指针。

对于数组来说也是如此,所以在内存中的结构数组看起来就像这样

[struct][struct][struct][struct][struct][struct][struct][struct]

作为一个类的数组看起来像这样

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

对于绝大多数应用程序来说,这种差别并不重要,但是在高性能代码中,这会影响内存中数据的局部性。 当你可以或应该使用结构时,使用类将大大增加CPU上的caching未命中数量,并会影响性能。

现代CPU做的最慢的事情不是紧缩数字,它是从内存中获取数据,并且L1caching命中比从RAM中读取数据要快很多倍。

  1. 在类中声明的事件通过一个锁(this)自动locking+ =和 – =访问,以使它们线程安全(静态事件locking在类的types上)。 在结构中声明的事件没有自动locking+ =和 – =访问。 因为你只能locking一个引用typesexpression式,所以一个结构的锁(this)将不起作用。

  2. 创build一个结构实例不会导致垃圾回收(除非构造函数直接或间接地创build一个引用types实例),而创build一个引用types实例会导致垃圾回收。

  3. 一个结构总是有一个内置的公共默认构造函数。

     class DefaultConstructor { static void Eg() { Direct yes = new Direct(); // Always compiles OK InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible //... } } 

    这意味着一个结构总是可实例化的,而一个类可能不是,因为它的所有构造函数都可以是私有的。

     class NonInstantiable { private NonInstantiable() // OK { } } struct Direct { private Direct() // Compile-time error { } } 
  4. 一个结构不能有一个析构函数。 析构函数只是对象的重载。变相伪装,作为值types的结构不受垃圾收集的限制。

     struct Direct { ~Direct() {} // Compile-time error } class InDirect { ~InDirect() {} // Compiles OK } And the CIL for ~Indirect() looks like this: .method family hidebysig virtual instance void Finalize() cil managed { // ... } // end of method Indirect::Finalize 
  5. 一个结构是隐式的,一个类不是。
    一个结构不能是抽象的,一个类可以。
    一个结构体不能在其构造函数中调用base(),而没有显式基类的类可以。
    一个结构体不能扩展另一个类,一个类可以。
    结构不能声明一个类可以保护的成员(例如,字段,嵌套types)。
    一个struct不能声明抽象函数成员,一个抽象类可以。
    一个struct不能声明虚函数成员,一个类可以。
    一个struct不能声明密封的函数成员,一个类可以。
    一个struct不能声明重载函数成员,一个类可以。
    这个规则的一个例外是一个结构可以重写System.Object的虚拟方法,即Equals()和GetHashCode()以及ToString()。

原始值types或结构types的每个variables或字段都包含该types的唯一实例,包括其所有字段(公共和私有)。 相比之下,引用types的variables或字段可以为空,或者可以引用其他地方存储的任何数量的其他引用也可以存在的对象。 结构的字段将被存储在与该结构types的variables或字段相同的位置,该variables或字段可能在堆栈中,也可能是另一个堆对象的一部分

创build一个原始值types的variables或字段将使用默认值创build它; 创build结构types的variables或字段将创build一个新实例,以默认方式创build其中的所有字段。 创build一个引用types的新实例将首先以默认的方式创build其中的所有字段,然后根据types运行可选的附加代码。

将一个基本types的variables或字段复制到另一个将复制该值。 将一个variables或结构types的字段复制到另一个将复制前一个实例的所有字段(公共和私有)到后一个实例。 复制一个variables或引用types的字段到另一个将导致后者引用与前者(如果有的话)相同的实例。

需要注意的是,在一些像C ++这样的语言中,一个types的语义行为与它的存储方式无关,但是.NET并不是这样。 如果一个types实现了可变值语义,那么将该types的一个variables复制到另一个variables中复制第一个variables的属性到第二个variables引用的另一个实例,并使用第二个variables的成员来变更它将导致第二个实例被更改,但不是第一个。 如果一个types实现可变引用语义,则将一个variables复制到另一个variables,并使用第二个variables的成员来改变该对象将影响第一个variables引用的对象; 具有不可变语义的types不允许突变,所以从语义上来说复制是创build一个新实例还是创build另一个引用。

在.NET中,值types可以实现任何上述语义,只要它们的所有字段都可以这样做。 然而,引用types只能实现可变引用语义或不可变语义; 具有可变引用types字段的值types仅限于实现可变引用语义或奇怪的混合语义。