.NET中的结构和类有什么区别?
当前回答
除了其他答案之外,还有一个基本的区别值得注意,那就是数据如何存储在数组中,因为这会对性能产生重大影响。
对于结构,数组包含结构的实例对于类,数组包含指向内存中其他位置的类实例的指针
因此,内存中的结构数组如下所示
[结构][结构]〔结构〕〔结构〕
而类数组看起来像这样
指针
对于一个类数组,您感兴趣的值不会存储在数组中,而是存储在内存的其他位置。
对于绝大多数应用程序来说,这种差异并不重要,但是,在高性能代码中,这将影响内存中数据的位置,并对CPU缓存的性能产生很大影响。在可以/应该使用结构的情况下使用类将大大增加CPU上的缓存未命中数。
现代CPU做的最慢的事情不是处理数字,而是从内存中提取数据,一级缓存命中速度比从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}");
}
其他回答
有一个有趣的“类vs结构”难题案例——当您需要从方法返回几个结果时:选择要使用的结果。如果你知道ValueTuple的故事,你就知道添加ValueTuple(结构)是因为它应该比Tuple(类)更有效。但这在数字上意味着什么?两个测试:一个是具有2个字段的结构/类,另一个是有8个字段的类型/类(维度大于4-从处理器节拍的角度来看,类应该比结构更有效,但当然也应该考虑GC负载)。
P.S.另一个特定案例“stuct or class with collections”的基准是:https://stackoverflow.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;
}
}
}
}
每个项目的简短摘要:
仅限班级:
可以支持继承是引用(指针)类型引用不能为空每个新实例的内存开销
仅结构:
无法支持继承是值类型按值传递(如整数)不能有空引用(除非使用了Nullable)每个新实例没有内存开销-除非“装箱”
类和结构:
复合数据类型通常用于包含一些具有某种逻辑关系的变量吗可以包含方法和事件可以支持接口
结构和类别之间的差异:
结构是值类型,而类是引用类型。结构存储在堆栈上,而类存储在堆值类型将其值保存在声明它们的内存中,但引用类型保存对内存中对象的引用。值类型在作用域丢失后立即销毁,而引用类型仅在作用域丢失后销毁变量。该对象随后被垃圾收集器销毁。将结构复制到另一个结构时,该结构的新副本创建。修改的结构不会影响其他结构。将一个类复制到另一个类时,它只复制参考变量。两个引用变量都指向堆上的同一对象。对一个变量所做的更改将影响另一个参考变量。结构不能有析构函数,但类可以有析构器。结构不能具有显式无参数构造函数,而类可以。结构不支持继承,但类支持支持从接口继承。结构为密封型。
在.NET中,有两类类型,引用类型和值类型。
结构是值类型,类是引用类型。
一般的区别是,引用类型存在于堆中,值类型存在于内联中,也就是说,在定义变量或字段的任何位置。
包含值类型的变量包含整个值类型值。对于结构,这意味着变量包含整个结构及其所有字段。
包含引用类型的变量包含指针,或对内存中实际值所在位置的引用。
首先,这有一个好处:
值类型始终包含值引用类型可以包含空引用,这意味着它们此刻根本不引用任何内容
在内部,引用类型被实现为指针,并且知道变量赋值是如何工作的,还有其他行为模式:
将值类型变量的内容复制到另一个变量中,将整个内容复制到新变量中,使这两个变量不同。换句话说,复制后,对其中一个的更改不会影响另一个将一个引用类型变量的内容复制到另一个变量中,就会复制引用,这意味着现在有两个引用指向实际数据的同一个存储位置。换句话说,在复制之后,更改一个引用中的数据似乎也会影响另一个引用,但这只是因为您实际上只是在两个地方查看相同的数据
当您声明变量或字段时,以下是两种类型的区别:
变量:值类型位于堆栈上,引用类型位于堆栈中,作为指向堆内存中实际内存所在位置的指针(请注意EricLipperts系列文章:堆栈是一个实现细节)类/结构字段:值类型完全位于类型内部,引用类型位于类型内部作为指向堆内存中实际内存所在位置的指针。
除了其他答案中描述的所有差异外:
结构不能具有显式无参数构造函数,而类可以结构不能有析构函数,而类可以结构不能从其他结构或类继承,而类可以从其他类继承。(结构和类都可以从接口实现。)
如果您正在观看一段视频,解释所有的差异,您可以查看第29部分-C#教程-C#中类和结构之间的差异。