.NET 中的结构和类有啥区别?
Posted
技术标签:
【中文标题】.NET 中的结构和类有啥区别?【英文标题】:What's the difference between struct and class in .NET?.NET 中的结构和类有什么区别? 【发布时间】:2010-09-05 23:55:58 【问题描述】:.NET 中的结构和类有什么区别?
【问题讨论】:
好问题,对我也有帮助。 【参考方案1】:在.NET中,有两类类型,引用类型和值类型。
结构是值类型,类是引用类型。
一般的区别是引用类型存在于堆上,而值类型存在于内联,也就是说,无论它是你定义的变量或字段。
包含值类型的变量包含整个值类型值。对于结构体,这意味着变量包含整个结构体及其所有字段。
一个包含引用类型的变量包含一个指针,或一个引用到内存中实际值所在的其他地方。
这有一个好处,首先:
值类型总是包含一个值 引用类型可以包含一个null-引用,这意味着它们现在根本不引用任何东西在内部,引用类型被实现为指针,知道这一点,知道变量赋值是如何工作的,还有其他行为模式:
将值类型变量的内容复制到另一个变量中,将整个内容复制到新变量中,使两者不同。换句话说,在复制之后,对其中一个的更改不会影响另一个 将引用类型变量的内容复制到另一个变量中,复制该引用,这意味着您现在有两个引用到相同的其他地方存储实际数据.换句话说,在复制之后,更改一个引用中的数据似乎也会影响另一个引用,但这只是因为您实际上只是在两个地方查看相同的数据当您声明变量或字段时,这两种类型的区别如下:
变量:值类型存在于栈中,引用类型存在于栈中作为指向实际内存所在的堆内存中某处的指针(尽管注意@987654321 @.) class/struct-field:值类型完全存在于类型内部,引用类型存在于类型内部,作为指向实际内存所在堆内存某处的指针.【讨论】:
为了完整起见,我应该提到 Eric Lippert 曾说过 the stack is an implementation detail,每当我在上面提到堆栈时,都要记住 Eric 的帖子。 这对 C++ 也有效吗? 另一个重要的区别是用法。来自 MSDN:“结构通常用于封装一小组相关变量,例如矩形的坐标。结构还可以包含构造函数、常量、字段、方法、属性、索引器、运算符、事件和嵌套类型,尽管如果有几个这样的成员是必需的,您应该考虑将您的类型改为类。” 这是一个代码示例,说明了类实例是通过引用分配的,而结构实例是通过值分配的:ideone.com/5yqg2u @KorayTugay 在 C++ 结构和类中是绝对等价的,除了一件事——默认访问限制(类默认是私有的,结构是公共的)【参考方案2】:每个的简短摘要:
仅限类:
可以支持继承 是引用(指针)类型 引用可以为空 每个新实例都有内存开销仅限结构:
不支持继承 是值类型 按值传递(如整数) 不能有空引用(除非使用 Nullable) 没有每个新实例的内存开销 - 除非“装箱”类和结构:
是复合数据类型,通常用于包含一些具有某种逻辑关系的变量 可以包含方法和事件 可以支持接口【讨论】:
这个答案的某些部分不太正确。类并不总是在堆上,结构也不总是在堆栈上。当前的例外包括类上的结构字段、匿名方法和 lambda 表达式中捕获的变量、迭代器块以及已经提到的装箱值。但是堆栈与堆分配是一个实现细节,可能会发生变化。 Eric lippart discusses this here。我投了反对票,但如果您更新,我很乐意将其删除。 struct 不支持从其他结构/类继承,但您可以在结构上实现接口。 当您声称结构 “每个新实例没有内存开销” 时,您可能想澄清您的意思。我的第一个解释是您声称 - 显然是荒谬的 - 结构使用零内存。然后我想也许你想说一个结构,不像一个类,它需要的内存与其成员字段的总和一样多,仅此而已。但后来我在 Google 上搜索了c# struct memory overhead
,发现 Hans Passant 的 this answer 说不,也不是这样。那么你的意思是做什么?
@MarkAmery 我对“无内存开销”的表达与你有相同的初始反应,但我认为 OP 指的是class
的实例是托管内存(已处理由垃圾收集器),而 struct
的实例不是。
“结构是按值传递的(如整数)”为假:所有变量都按值传递,引用类型也是如此。如果要通过引用传递变量,则必须使用“ref”关键字。 jonskeet.uk/csharp/parameters.html#ref【参考方案3】:
结构体和类的区别:
结构是值类型,而类是引用类型。 结构存储在堆栈中,而类存储在 堆。 值类型将它们的值保存在声明它们的内存中,但是 引用类型保存对内存中对象的引用。 值类型在范围丢失后立即销毁,而 引用类型仅在范围丢失后销毁变量。该对象稍后会被垃圾收集器销毁。 当您将一个结构复制到另一个结构中时,该结构的新副本 被创建。修改后的结构不会影响 其他结构。 当您将一个类复制到另一个类时,它只复制 参考变量。 两个引用变量都指向堆上的同一个对象。 对一个变量所做的更改将影响另一个参考变量。 结构不能有析构函数,但类可以有析构函数。 结构不能有显式的无参数构造函数,而类可以。结构不支持继承,但类支持。两个都 支持从接口继承。 结构是密封型的。【讨论】:
我发现这是一个详尽而简洁的答案。想了解更多关于每颗子弹的信息吗?去学习吧。 “结构存储在堆栈上而类存储在堆上”不一定正确。见***.com/questions/3542083/…【参考方案4】:在 .NET 中,结构和类声明区分引用类型和值类型。
当你传递一个引用类型时,实际上只存储了一个。所有访问实例的代码都在访问同一个。
当您传递一个值类型时,每个值类型都是一个副本。所有代码都在自己的副本上运行。
这可以用一个例子来展示:
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 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.
类可以什么都不是——引用可以指向一个空值。
结构是实际值——它们可以是空的,但不能为空。由于这个原因,结构总是有一个没有参数的默认构造函数——它们需要一个“起始值”。
【讨论】:
@T.Todua 是的,上面有更好的答案,我在提供这个答案后投了赞成票并选择了答案 - 这是我们仍在弄清楚规则时 SO 的早期测试版. 我不知道你是否正确理解了我,我真的赞成/接受你的答案(与上述答案相反),因为你有很好的例子(不仅仅是理论解释,而不是上述答案,只有理论解释,没有例子)。【参考方案5】:来自微软的Choosing Between Class and Struct ...
根据经验,框架中的大多数类型应该是 类。但是,在某些情况下, 值类型的特征使其更适合使用 结构。
✓ 考虑一个结构而不是一个类:
如果该类型的实例很小且通常寿命很短,或者通常嵌入在其他对象中。X 避免使用结构体,除非该类型具有以下所有 特点:
它在逻辑上表示单个值,类似于原始类型(int、double 等)。 它的实例大小小于 16 个字节。 它是不可变的。 (无法更改) 不必经常装箱。
【讨论】:
这应该是简洁的答案。【参考方案6】:我♥可视化,在这里我创建了一个来显示structs和classes之间的基本区别。
还有文本表示以防万一;)
+--------------------------------------------------+------+----------------------------------------------+
| Struct | | Class |
+--------------------------------------------------+------+----------------------------------------------+
| - 1 per Thread. | | - 1 per application. |
| | | |
| - Holds value types. | | - Holds reference types. |
| | | |
| - Types in the stack are positioned | | - No type ordering (data is fragmented). |
| using the LIFO principle. | | |
| | | |
| - Can't have a default constructor and/or | | - Can have a default constructor |
| finalizer(destructor). | | and/or finalizer. |
| | | |
| - Can be created with or without a new operator. | | - Can be created only with a new operator. |
| | | |
| - Can't derive from the class or struct | VS | - Can have only one base class and/or |
| but can derive from the multiple interfaces. | | derive from multiple interfaces. |
| | | |
| - The data members can't be protected. | | - Data members can be protected. |
| | | |
| - Function members can't be | | - Function members can be |
| virtual or abstract. | | virtual or abstract. |
| | | |
| - Can't have a null value. | | - Can have a null value. |
| | | |
| - During an assignment, the contents are | | - Assignment is happening |
| copied from one variable to another. | | by reference. |
+--------------------------------------------------+------+----------------------------------------------+
更多信息请看下面:
Classes and structs(官方文档)。 Choosing Between Class and Struct(官方文档)。【讨论】:
【参考方案7】:除了其他答案中描述的所有差异:
-
结构 cannot have an explicit parameterless constructor 而类可以
结构体cannot have destructors,而类可以
结构can't inherit 来自另一个结构或类,而一个类可以从另一个类继承。 (结构和类都可以从接口实现。)
如果您正在观看解释所有差异的视频,您可以查看 Part 29 - C# Tutorial - Difference between classes and structs in C#。
【讨论】:
.net 语言通常不允许结构定义无参数构造函数(由语言编译器决定是否允许)这一事实更重要的是,结构可以在没有运行任何类型的构造函数的情况下存在并暴露给外部世界(即使定义了无参数构造函数)。 .net 语言通常禁止结构的无参数构造函数的原因是为了避免这种构造函数有时运行有时不运行而导致的混乱。【参考方案8】:Struct | Class | |
---|---|---|
Type | Value-type | Reference-type |
Where | On stack / Inline in containing type | On Heap |
Deallocation | Stack unwinds / containing type gets deallocated | Garbage Collected |
Arrays | Inline, elements are the actual instances of the value type | Out of line, elements are just references to instances of the reference type residing on the heap |
Al-Del Cost | Cheap allocation-deallocation | Expensive allocation-deallocation |
Memory usage | Boxed when cast to a reference type or one of the interfaces they implement, Unboxed when cast back to value type (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) | No boxing-unboxing |
Assignments | Copy entire data | Copy the reference |
Change to an instance | Does not affect any of its copies | Affect all references pointing to the instance |
Mutability | Should be immutable | Mutable |
Population | In some situations | Majority of types in a framework should be classes |
Lifetime | Short-lived | Long-lived |
Destructor | Cannot have | Can have |
Inheritance | Only from an interface | Full support |
Polymorphism | No | Yes |
Sealed | Yes | When have sealed keyword (C#), or Sealed attribute (F#) |
Constructor | Can not have explicit parameterless constructors | Any constructor |
Null-assignments | When marked with nullable question mark | Yes (When marked with nullable question mark in C# 8+ and F# 5+ 1) |
Abstract | No | When have abstract keyword (C#), or AbstractClass attribute (F#) |
Member Access Modifiers |
public , private , internal
|
public , protected , internal , protected internal , private protected
|
1 在 F# 中不鼓励使用 null
,而是使用 Option 类型。
【讨论】:
这实际上非常出色:总结性和信息性。请记住至少校对一次您的答案 - 您在某些行中交换了结构和类的解释,还有一些错别字。 @ensisNoctis 对这些错误表示抱歉,感谢您的编辑。我应该重新阅读我的答案?【参考方案9】:类的实例存储在托管堆上。所有“包含”实例的变量都只是对堆上实例的引用。将对象传递给方法会导致传递引用的副本,而不是对象本身。
结构(技术上是值类型)存储在任何使用它们的地方,就像原始类型一样。运行时可以随时复制内容,而无需调用自定义的复制构造函数。将值类型传递给方法涉及复制整个值,同样无需调用任何可自定义的代码。
C++/CLI 名称可以更好地区分:“ref class”是第一个描述的类,“value class”是第二个描述的类。 C# 使用的关键字“class”和“struct”只是必须学习的东西。
【讨论】:
【参考方案10】:除了其他答案之外,还有一个值得注意的根本区别,那就是数据如何存储在数组中,因为这会对性能产生重大影响。
对于结构,数组包含结构的实例 对于类,数组包含指向内存中其他位置的类实例的指针所以结构数组在内存中看起来像这样
[struct][struct][struct][struct][struct][struct][struct][struct]
而类数组看起来像这样
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
对于类数组,您感兴趣的值不会存储在数组中,而是存储在内存中的其他位置。
对于绝大多数应用程序来说,这种差异并不重要,但是,在高性能代码中,这会影响内存中数据的局部性,并对 CPU 缓存的性能产生很大影响。在您可以/应该使用结构的情况下使用类将大大增加 CPU 上的缓存未命中次数。
现代 CPU 做的最慢的事情不是处理数字,而是从内存中获取数据,而 L1 缓存命中比从 RAM 中读取数据快很多倍。
这里有一些您可以测试的代码。在我的机器上,遍历类数组需要比结构数组长约 3 倍的时间。
private struct PerformanceStruct
public int i1;
public int i2;
private class PerformanceClass
public int i1;
public int i2;
private static void DoTest()
var structArray = new PerformanceStruct[100000000];
var classArray = new PerformanceClass[structArray.Length];
for (var i = 0; i < structArray.Length; i++)
structArray[i] = new PerformanceStruct();
classArray[i] = new PerformanceClass();
long total = 0;
var sw = new Stopwatch();
sw.Start();
for (var loops = 0; loops < 100; loops++)
for (var i = 0; i < structArray.Length; i++)
total += structArray[i].i1 + structArray[i].i2;
sw.Stop();
Console.WriteLine($"Struct Time: sw.ElapsedMilliseconds");
sw = new Stopwatch();
sw.Start();
for (var loops = 0; loops < 100; loops++)
for (var i = 0; i < classArray.Length; i++)
total += classArray[i].i1 + classArray[i].i2;
Console.WriteLine($"Class Time: sw.ElapsedMilliseconds");
【讨论】:
-1; “结构是值类型,因此它们存储一个值,类是引用类型,因此它们引用一个类。” 不清楚,对于尚未从其他方面理解它的任何人来说都不太可能有意义在这里回答,并且“对于一个类,包含类将只包含一个指向不同内存区域中的新类的指针。” 将类与类实例混淆。 @MarkAmery 我试图稍微澄清一下。我真正想要说明的一点是数组处理值和引用类型的方式的不同,以及这对性能的影响。我并没有试图重新解释什么是值和引用类型,因为在许多其他答案中都这样做了。【参考方案11】:结构与类
结构是值类型,所以存储在栈中,而类是引用类型,存储在堆中。
结构不支持继承和多态,但类支持两者。
默认情况下,所有结构成员都是公共的,但类成员默认情况下是私有的。
由于结构体是一种值类型,我们不能将null赋给结构体对象,但对于类则不然。
【讨论】:
关于“所有结构成员都是公共的”:如果我没记错的话,那是不正确的。 “类成员和结构成员的访问级别,包括嵌套类和结构,默认情况下是私有的。” msdn.microsoft.com/en-us/library/ms173121.aspx “我们不能将 null 分配给结构对象”是不正确的,因为您可以将 Nullable 类型与结构一起使用。【参考方案12】:嗯,对于初学者来说,结构是通过值而不是通过引用传递的。结构适用于相对简单的数据结构,而从架构的角度来看,通过多态性和继承,类具有更大的灵活性。
其他人可能会给你比我更多的细节,但是当我想要的结构很简单时,我会使用结构。
【讨论】:
类对象默认也是传值的 @Imad: 参考值,是的【参考方案13】:为了完整起见,使用Equals
方法时还有另一个区别,它被所有类和结构继承。
假设我们有一个类和一个结构:
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 坐标)。课程适合其他人。即使两个人的名字、身高、体重……相同,他们仍然是两个人。
【讨论】:
【参考方案14】:在类中声明的事件通过 lock(this) 自动锁定其 += 和 -= 访问权限,以使其线程安全(静态事件锁定在类的类型上)。在结构中声明的事件不会自动锁定其 += 和 -= 访问权限。结构的 lock(this) 不起作用,因为您只能锁定引用类型表达式。
创建结构实例不会导致垃圾回收(除非构造函数直接或间接创建引用类型实例),而创建引用类型实例会导致垃圾回收。
结构总是有一个内置的公共默认构造函数。
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
结构不能有析构函数。析构函数只是对象的覆盖。变相的终结,而结构作为值类型,不受垃圾回收的影响。
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
结构是隐式密封的,类不是。 结构不能是抽象的,类可以。 结构不能在其构造函数中调用 : base() 而没有显式基类的类可以。 一个结构不能扩展另一个类,一个类可以。 结构不能声明类可以声明的受保护成员(例如,字段、嵌套类型)。 结构不能声明抽象函数成员,抽象类可以。 结构不能声明虚函数成员,类可以。 结构不能声明密封函数成员,类可以。 结构不能声明覆盖函数成员,类可以。 此规则的一个例外是结构可以覆盖 System.Object 的虚拟方法,即 Equals()、GetHashCode() 和 ToString()。
【讨论】:
在什么情况下会使用带有结构的事件?我可以想象一个非常精心编写的程序可以以一种可行的方式将事件与结构一起使用,但前提是该结构从未被复制或按值传递,在这种情况下,它也可能是一个类。 @supercat 是的,结构中的非静态事件会很奇怪,它只对可变结构有用,而且事件本身(如果它是“类字段”事件) ) 将结构转换为“可变”类别,并在结构中引入引用类型的字段。结构中的非静态事件一定是邪恶的。 @JeppeStigNielsen:我能看到的唯一模式是,如果结构的目的是保持对它所针对的类对象的不可变引用,那么结构具有事件是有意义的。充当代理。但是,在这种情况下,自动事件将完全没用;相反,订阅和取消订阅事件必须传递给结构后面的类。我希望 .NET 有(或可以定义)一个“缓存盒”结构类型,其类型为Object
的初始为空隐藏字段,它将包含对该结构的盒装副本的引用。跨度>
@JeppeStigNielsen:在许多代理使用场景中,结构优于类;使用结构的最大问题是,在最终需要装箱的情况下,它通常最终会被推迟到内部循环。如果有办法避免结构被重复装箱,那么在更多使用场景中它们会比类更好。【参考方案15】:
如前所述:类是引用类型,而结构是具有所有后果的值类型。
作为规则框架设计指南建议在以下情况下使用结构而不是类:
实例大小小于 16 个字节 它在逻辑上表示单个值,类似于原始类型(int、double 等) 它是不可变的 不必经常装箱【讨论】:
【参考方案16】:除了访问说明符的基本区别和上面提到的很少之外,我想补充一些主要的区别,包括上面提到的几个与输出的代码示例,这将使参考和价值更加清晰
结构:
是值类型,不需要堆分配。 内存分配不同,存储在堆栈中 适用于小型数据结构 影响性能,当我们将值传递给方法时,我们将整个数据结构传递给堆栈。 构造函数只返回结构值本身(通常在堆栈上的临时位置),然后根据需要复制此值 每个变量都有自己的数据副本,对一个变量的操作不可能影响另一个变量。 不支持用户指定的继承,它们隐式继承自类型对象类:
引用类型值 存储在堆中 存储对动态分配对象的引用 使用 new 运算符调用构造函数,但不会在堆上分配内存 多个变量可能引用同一个对象 对一个变量的操作可能会影响另一个变量引用的对象代码示例
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 Object的初始值为:10
内部结构方法 Struct Object 的内部 Method 值为:20
Struct Object的方法调用后值为:10
类对象的初始值为:10
内部类方法 Class Object的Inside Method值为:20
Class Object的方法调用后值为:20
在这里你可以清楚地看到按值调用和按引用调用的区别。
【讨论】:
【参考方案17】:有一个有趣的“类与结构”难题的案例——当您需要从方法返回多个结果时:选择使用哪个。如果你知道 ValueTuple 的故事 - 你知道 ValueTuple (struct) 被添加是因为它应该比 Tuple (class) 更有效。但它在数字上意味着什么?两个测试:一个是具有 2 个字段的结构/类,另一个是具有 8 个字段的结构/类(维度大于 4 - 就处理器滴答而言,类应该比结构更有效,但当然也应该考虑 GC 负载)。
附:特定案例“带有集合的结构或类”的另一个基准是:https://***.com/a/45276657/506147
BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
[Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
Clr : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
Core : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
TestStructReturn | Clr | Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns | 4 | 0.0127 | 40 B |
TestClassReturn | Clr | Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns | 5 | 0.0229 | 72 B |
TestStructReturn8 | Clr | Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns | 8 | 0.0127 | 40 B |
TestClassReturn8 | Clr | Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns | 6 | 0.0305 | 96 B |
TestStructReturn | Core | Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns | 1 | 0.0127 | 40 B |
TestClassReturn | Core | Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns | 2 | 0.0229 | 72 B |
TestStructReturn8 | Core | Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns | 7 | 0.0127 | 40 B |
TestClassReturn8 | Core | Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns | 3 | 0.0305 | 96 B |
代码测试:
using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;
namespace Benchmark
//[Config(typeof(MyManualConfig))]
[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
[ClrJob, CoreJob]
[HtmlExporter, MarkdownExporter]
[MemoryDiagnoser]
public class BenchmarkStructOrClass
static TestStruct testStruct = new TestStruct();
static TestClass testClass = new TestClass();
static TestStruct8 testStruct8 = new TestStruct8();
static TestClass8 testClass8 = new TestClass8();
[Benchmark]
public void TestStructReturn()
testStruct.TestMethod();
[Benchmark]
public void TestClassReturn()
testClass.TestMethod();
[Benchmark]
public void TestStructReturn8()
testStruct8.TestMethod();
[Benchmark]
public void TestClassReturn8()
testClass8.TestMethod();
public class TestStruct
public int Number = 5;
public struct StructType<T>
public T Instance;
public List<string> List;
public int TestMethod()
var s = Method1(1);
return s.Instance;
private StructType<int> Method1(int i)
return Method2(++i);
private StructType<int> Method2(int i)
return Method3(++i);
private StructType<int> Method3(int i)
return Method4(++i);
private StructType<int> Method4(int i)
var x = new StructType<int>();
x.List = new List<string>();
x.Instance = ++i;
return x;
public class TestClass
public int Number = 5;
public class ClassType<T>
public T Instance;
public List<string> List;
public int TestMethod()
var s = Method1(1);
return s.Instance;
private ClassType<int> Method1(int i)
return Method2(++i);
private ClassType<int> Method2(int i)
return Method3(++i);
private ClassType<int> Method3(int i)
return Method4(++i);
private ClassType<int> Method4(int i)
var x = new ClassType<int>();
x.List = new List<string>();
x.Instance = ++i;
return x;
public class TestStruct8
public int Number = 5;
public struct StructType<T>
public T Instance1;
public T Instance2;
public T Instance3;
public T Instance4;
public T Instance5;
public T Instance6;
public T Instance7;
public List<string> List;
public int TestMethod()
var s = Method1(1);
return s.Instance1;
private StructType<int> Method1(int i)
return Method2(++i);
private StructType<int> Method2(int i)
return Method3(++i);
private StructType<int> Method3(int i)
return Method4(++i);
private StructType<int> Method4(int i)
var x = new StructType<int>();
x.List = new List<string>();
x.Instance1 = ++i;
return x;
public class TestClass8
public int Number = 5;
public class ClassType<T>
public T Instance1;
public T Instance2;
public T Instance3;
public T Instance4;
public T Instance5;
public T Instance6;
public T Instance7;
public List<string> List;
public int TestMethod()
var s = Method1(1);
return s.Instance1;
private ClassType<int> Method1(int i)
return Method2(++i);
private ClassType<int> Method2(int i)
return Method3(++i);
private ClassType<int> Method3(int i)
return Method4(++i);
private ClassType<int> Method4(int i)
var x = new ClassType<int>();
x.List = new List<string>();
x.Instance1 = ++i;
return x;
【讨论】:
【参考方案18】:结构是实际值 - 它们可以为空,但不能为空
确实如此,但请注意,从 .NET 2 开始,结构支持 Nullable 版本,并且 C# 提供了一些语法糖以使其更易于使用。
int? value = null;
value = 1;
【讨论】:
请注意,这只是语法糖,读取为 'Nullable(object)(default(int?)) == null
,您无法使用任何其他值类型,因为这里不仅仅是糖。唯一的糖是int?
for Nullable<int>
。
-1;这并没有解决结构和类之间有什么区别的问题,因此,它应该是对您要回复的答案的评论,而不是单独的答案。 (尽管 2008 年 8 月的网站规范可能有所不同!)【参考方案19】:
原始值类型或结构类型的每个变量或字段都包含该类型的唯一实例,包括其所有字段(公共和私有)。相比之下,引用类型的变量或字段可能为空,或者可能引用存储在其他地方的对象,也可能存在任何数量的其他引用。结构的字段将存储在与该结构类型的变量或字段相同的位置,它们可能在堆栈上,也可能是另一个堆对象的一部分。
创建原始值类型的变量或字段将使用默认值创建它;创建结构类型的变量或字段将创建一个新实例,并以默认方式创建其中的所有字段。创建引用类型的新实例首先会以默认方式在其中创建所有字段,然后根据类型运行可选的附加代码。
将原始类型的一个变量或字段复制到另一个将复制该值。将结构类型的一个变量或字段复制到另一个会将前一个实例的所有字段(公共和私有)复制到后一个实例。将一个引用类型的变量或字段复制到另一个变量或字段将导致后者引用与前者相同的实例(如果有)。
需要注意的是,在某些语言(如 C++)中,类型的语义行为与其存储方式无关,但对于 .NET 而言并非如此。如果一个类型实现了可变值语义,则将该类型的一个变量复制到另一个变量会将第一个变量的属性复制到另一个实例,由第二个实例引用,并使用第二个实例的成员对其进行变异将导致第二个实例被更改,但不是第一个。如果一个类型实现了可变引用语义,将一个变量复制到另一个变量并使用第二个变量的成员来改变对象将影响第一个变量引用的对象;具有不可变语义的类型不允许突变,因此复制是否创建新实例或创建对第一个实例的另一个引用在语义上并不重要。
在 .NET 中,值类型可以实现上述任何语义,前提是它们的所有字段都可以这样做。然而,引用类型只能实现可变引用语义或不可变语义;具有可变引用类型字段的值类型仅限于实现可变引用语义或奇怪的混合语义。
【讨论】:
以上是关于.NET 中的结构和类有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章