class / struct-field:value类型完全位于类型内部,引用类型位于类型内部,作为指向实际内存所在的堆内存中某处的指针 .
9
每个简短摘要:
Classes Only:
可以支持继承
是引用(指针)类型
引用可以为null
每个新实例都有内存开销
Structs Only:
无法支持继承
是值类型
按值传递(如整数)
不能有空引用(除非使用Nullable)
每个新实例没有内存开销 - 除非'boxed'
Both Classes and Structs:
复合数据类型通常用于包含一些具有某种逻辑关系的变量
可以包含方法和事件
可以支持接口
157
在.NET中,struct和class声明区分引用类型和值类型 .
传递引用类型时,实际只存储了一个 . 访问实例的所有代码都访问同一个代码 .
传递值类型时,每个都是副本 . 所有代码都在自己的副本上工作 .
这可以通过一个例子来展示:
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.
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;
}
}
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
+-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | 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 |
| Aldel Cost | Cheap allocation-deallocation | Expensive allocation-deallocation |
| Memory usage | Boxed when cast to a reference type or one of the interfaces they implement, | No boxing-unboxing |
| | Unboxed when cast back to value type | |
| | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) | |
| 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 |
| 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+) |
| Abstract | No | When have abstract keyword |
| Access Modifiers | public, private, internal | public, protected, internal, protected internal, private protected |
+-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
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中,有两类类型,引用类型和值类型 .
结构是值类型,类是引用类型 .
一般的区别是引用类型存在于堆上,并且值类型保持内联,即,定义了变量或字段的任何位置 .
包含值类型的变量包含整个值类型值 . 对于结构,这意味着变量包含整个结构及其所有字段 .
包含引用类型的变量包含指针或对实际值所在的内存中其他位置的引用 .
这有一个好处,首先:
值类型始终包含值
引用类型可以包含空引用,这意味着它们目前根本不引用任何内容
在内部,引用类型被实现为指针,并且知道并且知道变量赋值如何工作,还有其他行为模式:
将值类型变量的内容复制到另一个变量中,将整个内容复制到新变量中,使两者不同 . 换句话说,在复制之后,对一个的更改不会影响另一个
将引用类型变量的内容复制到另一个变量中,复制引用,这意味着您现在有两个对实际数据的其他位置存储的引用 . 换句话说,在复制之后,更改一个引用中的数据也会影响另一个引用,但这只是因为您实际上只是在同一个数据中查看相同的数据
声明变量或字段时,这两种类型的区别如下:
变量:值类型存在于堆栈中,引用类型作为指向堆内存中某处(实际内存所在的位置)的指针存在于堆栈中(尽管注意Eric Lipperts article series: The Stack Is An Implementation Detail . )
class / struct-field:value类型完全位于类型内部,引用类型位于类型内部,作为指向实际内存所在的堆内存中某处的指针 .
每个简短摘要:
Classes Only:
可以支持继承
是引用(指针)类型
引用可以为null
每个新实例都有内存开销
Structs Only:
无法支持继承
是值类型
按值传递(如整数)
不能有空引用(除非使用Nullable)
每个新实例没有内存开销 - 除非'boxed'
Both Classes and Structs:
复合数据类型通常用于包含一些具有某种逻辑关系的变量
可以包含方法和事件
可以支持接口
在.NET中,struct和class声明区分引用类型和值类型 .
传递引用类型时,实际只存储了一个 . 访问实例的所有代码都访问同一个代码 .
传递值类型时,每个都是副本 . 所有代码都在自己的副本上工作 .
这可以通过一个例子来展示:
对于一个课程,这将是不同的
类可以是空的 - 引用可以指向null .
结构是实际值 - 它们可以为空但从不为空 . 因此,结构总是有一个没有参数的默认构造函数 - 它们需要一个'起始值' .
除了其他答案中描述的所有差异:
结构cannot have an explicit parameterless constructor而一类可以
结构cannot have destructors,而一个类可以
从另一个结构或类中构造can't inherit,而类可以从另一个类继承 . (结构和类都可以从接口实现 . )
如果您正在阅读解释所有差异的视频,可以查看Part 29 - C# Tutorial - Difference between classes and structs in C# .
类的实例存储在托管堆上 . 包含'实例的所有变量只是对堆上实例的引用 . 将对象传递给方法会导致传递的引用副本,而不是对象本身 .
结构(技术上,值类型)存储在任何地方,就像原始类型一样 . 运行时可以随时复制内容,而无需调用自定义的复制构造函数 . 将值类型传递给方法涉及复制整个值,同样不需要调用任何可自定义的代码 .
通过C / CLI名称可以更好地区分:“ref class”是首先描述的类,“value class”是第二个描述的类 . C#使用的关键字“class”和“struct”只是必须学习的东西 .
来自微软的Choosing Between Class and Struct ......
结构和类之间的区别:
Structs are value type 而 Classes are reference type .
Structs are stored on the stack 而 Classes are stored on the heap .
值类型在声明它们的内存中保存它们的值,但引用类型包含对对象内存的引用 .
范围丢失后
Value types destroyed immediately ,而引用类型仅在范围丢失后输出destroy . 该对象稍后被垃圾收集器破坏 .
将struct复制到另一个struct时,创建该struct的新副本,修改一个struct不会影响另一个struct的值 .
将类复制到另一个类时,它只复制引用变量 .
引用变量都指向堆上的同一对象 . 更改为一个变量将影响另一个参考变量 .
Structs can not have destructors ,但类可以有析构函数 .
Structs can not have explicit parameterless constructors 而一个类可以结构化不支持继承,但类可以 . 两者都支持从接口继承 .
Structs are sealed type .
为了使其完整,使用
Equals
方法时会有另一个区别,该方法由所有类和结构继承 .让我们说我们有一个类和一个结构:
在Main方法中,我们有4个对象 .
然后:
So ,结构适用于类似数字的对象,如点(保存x和y坐标) . 课程适合其他人 . 即使2个人有相同的名字,身高,体重......,他们仍然是2个人 .
Structure vs Class
结构是一种值类型,因此它存储在堆栈中,但类是引用类型并存储在堆上 .
结构不支持继承和多态,但是一个类支持两者 .
默认情况下,所有struct成员都是公共的,但类成员默认是私有的 .
由于结构是值类型,因此我们不能将null分配给结构对象,但类不是这种情况 .
除了访问说明符的基本区别,以及上面提到的几个差异之外,我想补充一些主要的差异,包括上面提到的几个与带输出的代码示例,这将更清楚地了解参考和值
Structs:
是值类型,不需要堆分配 .
内存分配不同,存储在堆栈中
适用于小型数据结构
影响性能,当我们将值传递给方法时,我们传递整个数据结构,并将所有数据结构传递给堆栈 .
构造函数只返回结构值本身(通常在堆栈上的临时位置),然后根据需要复制该值
每个变量都有自己的数据副本,一个操作不可能影响另一个 .
不支持用户指定的继承,它们隐式继承自类型对象
Class:
参考类型值
存储在堆中
存储对动态分配对象的引用
使用new运算符调用构造函数,但不会在堆上分配内存
多个变量可能引用同一个对象
对一个变量的操作可能会影响另一个变量引用的对象
Code Sample
Output
Struct Object的初始值为:10
内部结构方法内部结构对象的方法值为:20
After Method call value of Struct Object is: 10
Class Object的初始值为:10
内部类方法内部类对象的方法值为:20
After Method call value of Class Object is: 20
在这里,您可以清楚地看到按值调用和按引用调用之间的区别 .
在类中声明的事件的=和 - =访问权限通过锁(this)自动锁定,以使它们的线程安全(静态事件被锁定在类的类型上) . 在结构中声明的事件没有自动锁定其=和 - =访问权限 . 由于只能锁定引用类型表达式,因此结构的锁(this)将不起作用 .
创建结构实例不能导致垃圾收集(除非构造函数直接或间接创建引用类型实例),而创建引用类型实例可能导致垃圾收集 .
结构总是有一个内置的公共默认构造函数 .
这意味着结构总是可实例化的,而类可能不是,因为它的所有构造函数都可以是私有的 .
结构不能是抽象的,类可以 .
struct不能在其构造函数中调用:base(),而没有显式基类的类可以 .
结构不能扩展另一个类,类可以 .
结构不能声明类可以保护受保护的成员(例如,字段,嵌套类型) .
结构不能声明抽象函数成员,抽象类可以 .
结构不能声明虚函数成员,类可以 .
结构不能声明密封的函数成员,类可以 .
struct不能声明覆盖函数成员,类可以 .
此规则的一个例外是struct可以覆盖System.Object的虚方法,即Equals()和GetHashCode()以及ToString() .
如前所述:类是引用类型,而Structs是具有所有后果的值类型 .
作为规则的缩影,框架设计指南建议在以下情况下使用Structs而不是类:
它的实例大小小于16个字节
它逻辑上表示单个值,类似于原始类型(int,double等)
这是不可改变的
不必频繁装箱
为了增加其他答案,有一个值得注意的根本区别,那就是它如何存储在内存中 . 这可能会对阵列的性能产生重大影响 . 结构是值类型,因此它们将值存储在它们所指向的内存区域中,类是引用类型,因此它们引用它们所指向的内存区域中的类,实际值存储在其他位置 .
使用结构,在包含的类中分配内存以存储数据 .
对于类,包含类将只包含指向不同内存区域中新类的指针 .
对于数组也是如此,因此结构数组在内存中看起来像这样
[struct][struct][struct][struct][struct][struct][struct][struct]
类的数组看起来像这样
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
您感兴趣的实际值实际上并不存储在数组中,而是存储在内存中 .
对于绝大多数应用程序而言,这种差异并不重要,但是,在高性能代码中,这会影响内存中数据的位置,并对CPU缓存的性能产生很大影响 . 在可能/应该使用结构时使用类将大大增加CPU上的高速缓存未命中数 .
现代CPU所做的最慢的事情不是紧急数字,它是从内存中获取数据,而L1缓存命中比从RAM读取数据快许多倍 .
这是事实,但是请注意,从.NET 2开始,结构体支持Nullable版本,C#提供一些语法糖以使其更易于使用 .
有一个有趣的“类与结构”难题 - 当你需要从方法返回几个结果时的情况:选择使用哪个 . 如果你知道ValueTuple的故事 - 你知道ValueTuple(struct)被添加了,因为它应该比Tuple(class)更有效 . 但它在数字上意味着什么?两个测试:一个是struct / class,有2个字段,另一个是struct / class,有8个字段(维度大于4 - class应该比struct更加有效处理器滴答,但当然GC加载也应该考虑) .
附:特定情况'sturct or class with collections'的另一个基准是:https://stackoverflow.com/a/45276657/506147
代码测试:
原始值类型或结构类型的每个变量或字段都包含该类型的唯一实例,包括其所有字段(公共和私有) . 相反,引用类型的变量或字段可以保持为空,或者可以指代存储在别处的对象,也可以存在任何数量的其他引用 . 结构的字段将存储在与该结构类型的变量或字段相同的位置,该变量或字段可以在堆栈上,也可以是另一个堆对象的一部分 .
创建原始值类型的变量或字段将使用默认值创建它;创建结构类型的变量或字段将创建一个新实例,以默认方式在其中创建所有字段 . 创建引用类型的新实例将首先以默认方式创建其中的所有字段,然后根据类型运行可选的附加代码 .
将一个基本类型的变量或字段复制到另一个变量或字段将复制该值 . 将一个变量或结构类型的字段复制到另一个变量或字段会将前一个实例的所有字段(公共和私有)复制到后一个实例 . 复制一个变量或字段引用类型到另一个将导致后者引用与前者相同的实例(如果有的话) .
值得注意的是,在某些语言(如C语言)中,类型的语义行为与其存储方式无关,但对于.NET则不然 . 如果一个类型实现了可变值语义,那么将该类型的一个变量复制到另一个变量将第一个的属性复制到另一个实例(由第二个引用),并使用第二个的成员进行变异,这将导致第二个实例被更改,但不是第一个 . 如果一个类型实现了可变引用语义,将一个变量复制到另一个变量并使用第二个变量成员来改变该对象将影响第一个变量引用的对象;具有不可变语义的类型不允许变异,因此从语义上讲,复制是创建新实例还是创建对第一个实例的另一个引用无关紧要 .
在.NET中,值类型可以实现上述任何语义,前提是它们的所有字段都可以这样做 . 但是,引用类型只能实现可变引用语义或不可变语义;具有可变引用类型字段的值类型仅限于实现可变引用语义或奇怪的混合语义 .