区分C# 中的 Struct 和 Class

翻译自 Manju lata Yadav 2019年6月2日 的博文 《Difference Between Struct And Class In C#》,补充了一些内容和示例。

结构体(struct)是类(class)的轻量级版本。结构体是值类型,可用于创建行为类似于内置类型的对象。

比较

结构体和类共享许多特性,但与类相比有以下局限性。

  • 结构体不能有默认构造函数(无参构造函数)或析构函数,构造函数中必须给所有字段赋值。
 public struct Coords
 {
   public double x;
   public double y;

   public Coords() //错误,不允许无参构造函数
   {
     this.x = 3;
     this.y = 4;
   }

   public Coords(double x) //错误,构造函数中必须给所有字段赋值
   {
     this.x = x;
   }

   public Coords(double x) //这个是正确的
   {
     this.x = x;
     this.y = 4;
   }

   public Coords(double x, double y) //这个是正确的
   {
     this.x = x;
     this.y = y;
   }
 }
  • 结构体是值类型,在赋值时进行复制。
  • 结构体是值类型,而类是引用类型。
  • 结构体可以在不使用 new 操作符的情况下实例化。 例如:
 public struct Coords
 {
   public double x;
   public double y;
 }

 static void Main()
 {
   Coords p;
   p.x = 3;
   p.y = 4;
   Console.WriteLine($"({p.x}, {p.y})"); // 输出: (3, 4)
 }
  • 结构体不能继承于另一个结构体或者类,类也不能继承结构体。所有结构体都直接继承于抽象类 System.ValueType,System.ValueType 又继承于 System.Object
  • 结构体不能是基类,因此,结构体不能是 abstract 的,且总是隐式密封的(sealed)。
  • 不允许对结构体使用抽象(abstract)和密封(sealed)修饰符,也不允许对结构体成员使用 protectedprotected internal 修饰符。
  • 结构体中的函数成员不能是抽象的(abstract)或虚的(virtual),重写(override)修饰符只允许重写从 System.ValueType 继承的方法。
  • 结构体中不允许实例属性或字段包含初始值设定项。但是,结构体允许静态属性或字段包含初始值设定项。 例如:
 public struct Coords
 {
   public double x = 4; //错误, 结构体中初始化器不允许实例字段设定初始值
   public static double y = 5; // 正确
   public static double z { get; set; } = 6; // 正确
 }
  • 结构体可以实现接口。
  • 结构体可以用作 nullable type(即:Nullable<T> 中的 T),对其赋值 null 值,参考【Nullable<T> Struct】

什么时候使用结构体或类?

要回答这个问题,我们应该很好地理解它们的差异。

序号 结构体(struct) 类(class)
1 结构体是值类型,可以在栈(stack)上分配,也可以在包含类型中内联分配。 类是引用类型,在堆(heap)上分配并垃圾回收。
2 值类型的分配和释放通常比引用类型的分配和释放更节约成本。 大的引用类型的赋值比大的值类型的赋值成本更低。
3 在结构体中,每个变量都包含自己的数据副本(ref 和 out 参数变量除外),对一个变量的操作不会影响另一个变量。 在类中,两个变量可以包含同一对象的引用,对一个变量的任何操作都会影响另一个变量。

这样,结构体(struct)只能在确定以下情形时使用:

  • 它在逻辑上表示单个值,比如基本类型(int, double,等等)。
  • 它是不可变的(immutable)。
  • 它不会频繁地装箱和拆箱。

在所有其他情形,应该将类型定义为类(class)。

结构体示例:

struct Location
{
  public int x, y;
  public Location(int x, int y)
  {
    this.x = x;
    this.y = y;
  }
}

static void Main()
{
  Location a = new Location(20, 20);
  Location b = a;
  a.x = 100;
  Console.WriteLine(b.x);
}

输出将是 20。“b” 的值是 “a” 的副本,因此 “b” 不受 “a.x” 更改的影响。但是在类中,输出将是 100,因为变量 “a” 和 “b” 引用同一个对象。

以下为译者补充

结构体实例与类实例

结构体实例的内存在栈(stack)上进行分配,所占用的内存随声明它的类型或方法一起回收。 这就是在赋值时要复制结构体的一个原因。 相比之下,类实例的内存在堆(heap)上进行分配,当对类实例的所有引用都超出范围时,为该类实例分配的内存将由公共语言运行时自动回收(垃圾回收)。

结构体实例的值相等性

两个结构体实例的比较是基于值的比较,而类实例的比较则是对其引用的比较。

若要确定两个结构体实例中的实例字段是否具有相同的值,可使用 ValueType.Equals 方法。 由于所有结构都隐式继承自 System.ValueType,因此可以直接在其对象上调用该方法,如以下示例所示:

public struct Person
{
  public string Name;
  public int Age;
  public Person(string name, int age)
  {
    Name = name;
    Age = age;
  }
}

static void Main()
{
  Person p1 = new Person("技术译站", 100);
  Person p2;
  p2.Name = "技术译站";
  p2.Age = 100;

  if (p2.Equals(p1))
    Console.WriteLine("p2 和 p1 有相同的值。");

  Console.ReadKey();
}
// 输出: p2 和 p1 有相同的值。

System.ValueType 是值类型的隐式基类, 它的 Equals 使用反射实现,因为它必须能够确定任何结构体中有哪些字段。 在创建自己的结构体时,重写 Equals 方法可以提供特定于你的类型的高效求等算法。

“基于值的相等”这一点和 C# 9.0 中新增的记录(record) 类型具有相似之处

作者 : Manju lata Yadav

译者 : 技术译民

出品 : 技术译站

链接 : 英文原文

以上就是区分C# 中的 Struct 和 Class的详细内容,更多关于C# 中 Struct 和 Class的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#中struct和class的区别详解

    本文详细分析了C#中struct和class的区别,对于C#初学者来说是有必要加以了解并掌握的. 简单来说,struct是值类型,创建一个struct类型的实例被分配在栈上.class是引用类型,创建一个class类型实例被分配在托管堆上.但struct和class的区别远不止这么简单. 概括来讲,struct和class的不同体现在: ● 类是引用类型,struct是值类型 ● 在托管堆上创建类的实例,在栈上创建struct实例 ● 类实例的赋值,赋的是引用地址,struct实例的赋值,赋的是

  • 区分C# 中的 Struct 和 Class

    翻译自 Manju lata Yadav 2019年6月2日 的博文 <Difference Between Struct And Class In C#>,补充了一些内容和示例. 结构体(struct)是类(class)的轻量级版本.结构体是值类型,可用于创建行为类似于内置类型的对象. 比较 结构体和类共享许多特性,但与类相比有以下局限性. 结构体不能有默认构造函数(无参构造函数)或析构函数,构造函数中必须给所有字段赋值. public struct Coords { public doub

  • 区分c++中的声明与定义

    C++编码过程中,我们经常谈及"定义"和"声明",二者是编程过程中的基本概念.我们需要使用一个变量.类型(类.结构体.枚举.共用体)或者函数时,我们需要提前定义和声明.定义和声明的过程,就像我们向图书馆借阅书籍一般,需要先完成书籍的印刷,即创造出书籍,这是一个定义的过程,有了书籍,我们需要到图书馆完成借阅的登记手续,这是申明的过程.完成了申明,我们有了使用书籍的权限,就可以尽情的畅游在知识的海洋.如果说书籍是自己委托印刷厂印刷的,那么你无需向他人借阅,即无需声明,可

  • C#中结构(struct)的部分初始化和完全初始化实例分析

    本文实例分析了C#中结构(struct)的部分初始化和完全初始化,分享给大家供大家参考.具体分析如下: 假设有这样一个值类型struct,如下所示: public struct Size { public int Length; public int Width; public int Area() { return Length*Width; } } 一.客户端,给所有struct字段初始化后调用方法 class Program { static void Main(string[] args

  • C++中关键字Struct和Class的区别

    Struct和Class的区别 今天这篇博文主要讲解在C++中关键字struct和class的区别.这篇博文,将会系统的将这两个关键字的不同面进行详细的讲解. 从语法上来讲,class和struct做类型定义时只有两点区别: 1.默认继承权限,如果不指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理: 2.成员的默认访问权限.class的成员默认是private权限,struct默认是public权限.以上两点也是struct和class最基本的

  • 区分java中String+String和String+char

    我们来考虑一个关于java中String的问题: "abc" + '/'和 "abc" + "/"的区别. 通过这个例子, 我们可以顺便练习一下JDK工具中javap的用法, 原问题是这样的: 把斜杠/当作字符或字符串有什么区别呢? 一个是当作基本数据类型char,一个是对象String.具体有什么区别呢? 当作字符效率会更高吗? String str = "abc" + '/'; 和 String str = "a

  • 如何区分JAVA中的equals与==

    java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型. byte,short,char,int,long,float,double,boolean  他们之间的比较,应用双等号(==),比较的是他们的值. 基本数据类型比较 == 和 Equals 两者都是比较值:equals()与==都是java中用于进行比较的,返回boolean值,不同的是equals()是Object类中定义的一个方法,==是一个比较运算符.下面是equals()在Object中的源码: public b

  • 如何区分C++中的inline和#define宏

    (1)什么是内联函数? 内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内. (2)为什么要引入内联函数? 当然,引入内联函数的主要目的是:解决程序中函数调用的效率问题. 另外,前面我们讲到了宏,里面有这么一个例子: #define ABS(x) ((x)>0? (x):-(x)) 当++i出现时,宏就会歪曲我们的意思,换句话说就是:宏的定义很容易产生二意性. (3)为什么inline能取代宏? 1. inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替

  • 如何区分JAVA中的throws和throw

    throws和throw: throws:用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁调用我我就抛给谁. 用在方法声明后面,跟的是异常类名 可以跟多个异常类名,用逗号隔开 表示抛出异常,由该方法的调用者来处理 throws表示出现异常的一种可能性,并不一定会发生这些异常 throw:则是用来抛出一个具体的异常类型. 用在方法体内,跟的是异常对象名 只能抛出一个异常对象名 表示抛出异常,由方法体内的语句处理 throw则是抛出了异常,执行throw则一定抛出了某种异常 分别

  • 区分python中的进程与线程

    今天整理的文章是给大家梳理Python的进程与线程的区别,没什么代码,希望大家能清楚知道他们的区别,什么情况用线程,什么情况用进程做到心中有数,希望大家能熟练掌握. 进程的基本概念 概念 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要使用的资源:进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标

  • 区分shell中的 反引号、$()和${}

    1.反引号与$()用于命令替换 反引号和$()的作用相同,用于命令替换(command substitution),即完成引用的命令的执行,将其结果替换出来,与变量替换差不多.比如: echo `date '--date=1 hour ago' +%Y-%m-%d-%H` #或者 echo $(date '--date=1 hour ago' +%Y-%m-%d-%H) 输出结果是相同的: 2019-02-02-09. 在编写Shell脚本时建议使用$(),原因主要有: (1)反引号与单引号外形

随机推荐