.NET中struct和class之间有什么区别?
答案 0 :(得分:954)
在.NET中,有两类类型,引用类型和值类型。
结构是值类型,类是引用类型。
一般区别是引用类型存在于堆上,值类型生活在内联中,也就是说,只要定义了变量或字段。
包含值类型的变量包含整个值类型值。对于结构,这意味着变量包含整个结构及其所有字段。
包含引用类型的变量包含指针或引用到内存中实际值所在的其他位置。
这有一个好处,首先是:
在内部,引用类型被实现为指针,并且知道并且知道变量赋值如何工作,还有其他行为模式:
声明变量或字段时,这两种类型的区别如下:
答案 1 :(得分:178)
每个的简短摘要:
仅限课程:
仅限结构:
类和结构:
答案 2 :(得分:32)
在.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.
类可以是空的 - 引用可以指向null。
结构是实际值 - 它们可以为空但从不为空。由于这个原因,结构总是有一个没有参数的默认构造函数 - 它们需要一个'起始值'。
答案 3 :(得分:18)
除了其他答案中描述的所有差异:
如果您正在浏览解释所有差异的视频,则可以查看 Part 29 - C# Tutorial - Difference between classes and structs in C# 。
答案 4 :(得分:18)
来自微软的Choosing Between Class and Struct ...
根据经验,框架中的大多数类型应该是 类。但是,有些情况下 值类型的特征使其更适合使用 结构
✓ 考虑结构而不是类:
- 如果该类型的实例很小并且通常是短暂的,或者通常嵌入在其他对象中。
X 避免结构,除非该类型具有以下全部 特性:
- 它逻辑上表示单个值,类似于原始类型(int,double等)。
- 它的实例大小小于16个字节。
- 这是不可改变的。 (无法更改)
- 不必频繁装箱。
答案 5 :(得分:14)
类的实例存储在托管堆上。包含'实例的所有变量只是对堆上实例的引用。将对象传递给方法会导致传递引用的副本,而不是对象本身。
结构(技术上,值类型)存储在任何地方,就像原始类型一样。运行时可以随时复制内容,而无需调用自定义的复制构造函数。将值类型传递给方法涉及复制整个值,同样不需要调用任何可自定义的代码。
C ++ / CLI名称的区别更好:“ref class”是首先描述的类,“value class”是第二个描述的类。 C#使用的关键字“class”和“struct”只是必须学习的东西。
答案 6 :(得分:14)
结构和类之间的区别:
答案 7 :(得分:9)
我♥可视化,这里我创建了一个可视化,以显示structs和classes之间的基本区别。
有关更多信息,请参见以下内容:
答案 8 :(得分:6)
结构与类
结构是一种值类型,因此它存储在堆栈中,但是类是引用类型并存储在堆上。
结构不支持继承和多态,但是类支持两者。
默认情况下,所有struct成员都是公共的,但类成员默认是私有的。
由于结构是值类型,我们不能将null赋给结构对象,但对于类来说则不是这样。
答案 9 :(得分:6)
为了使其完整,使用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坐标)。课程适合其他人。即使2个人有相同的名字,身高,体重......,他们仍然是2个人。
答案 10 :(得分:5)
要添加其他答案,有一个值得注意的根本区别,那就是它如何存储在内存中。这可能会对阵列的性能产生重大影响。结构是值类型,因此它们将值存储在它们指向的内存区域中,类是引用类型,因此它们引用它们指向的内存区域中的类,实际值存储在其他位置。 / p>
对于数组也是如此,因此结构数组在内存中看起来像这样
[struct][struct][struct][struct][struct][struct][struct][struct]
类的数组看起来像这样
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
您感兴趣的实际值实际上并不存储在数组中,而是存储在内存中。
对于绝大多数应用程序而言,这种差异并不重要,但是,在高性能代码中,这会影响内存中数据的位置,并对CPU缓存的性能产生很大影响。在可能/应该使用结构时使用类将大大增加CPU上的高速缓存未命中数。
现代CPU所做的最慢的事情不是紧急数字,它是从内存中获取数据,而L1缓存命中比从RAM读取数据快许多倍。
答案 11 :(得分:4)
嗯,对于初学者来说,结构是通过值而不是通过引用传递的。结构对于相对简单的数据结构是有益的,而从架构的角度来看,类通过多态和继承具有更大的灵活性。
其他人可能会给你提供比我更多的细节,但是当我想要的结构很简单时,我会使用结构。
答案 12 :(得分:3)
在类中声明的事件通过锁(this)自动锁定其+ =和 - =访问权限,以使其线程安全(静态事件在类的类型上被锁定)。在结构中声明的事件没有自动锁定其+ =和 - =访问权限。结构的锁(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(),而没有显式基类的类可以。
结构不能扩展另一个类,类可以。
结构不能声明类可以保护受保护的成员(例如,字段,嵌套类型)。
结构不能声明抽象函数成员,抽象类可以。
结构不能声明虚函数成员,类可以。
结构不能声明密封的函数成员,类可以。
struct不能声明覆盖函数成员,类可以。
此规则的一个例外是结构可以覆盖System.Object的虚方法,即Equals(),GetHashCode()和ToString()。
答案 13 :(得分:3)
除了访问说明符的基本区别,以及上面提到的几个差异之外,我想补充一些主要的差异,包括上面提到的几个与带有输出的代码示例,这将更清楚地了解参考和值
结构:
<强>类别:强>
代码示例
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的Inside Method值为:20
结构对象的方法调用值为:10
Class Object的初始值为:10
内部类方法 Class Object的Inside Method值为:20
类对象的方法调用值为:20
在这里,您可以清楚地看到按值调用和按引用调用之间的区别。
答案 14 :(得分:3)
如前所述:类是引用类型,而Structs是具有所有后果的值类型。
作为规则的缩影框架设计指南建议在以下情况下使用Structs而不是类:
答案 15 :(得分:2)
+-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | 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 |
+-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
答案 16 :(得分:1)
结构是实际值 - 它们可以为空但从不为空
这是事实,但是请注意,从.NET 2开始,结构体支持Nullable版本,C#提供一些语法糖,使其更易于使用。
int? value = null;
value = 1;
答案 17 :(得分:1)
There is one interesting case of "class vs struct" puzzle - situation when you need to return several results from the method: choose which to use. If you know the ValueTuple story - you know that ValueTuple (struct) was added because it should be more effective then Tuple (class). But what does it mean in numbers? Two tests: one is struct/class that have 2 fields, other with struct/class that have 8 fields (with dimension more then 4 - class should become more effective then struct in terms processor ticks, but of course GC load also should be considered).
P.S. Another benchmark for specific case 'sturct or class with collections' is there: https://stackoverflow.com/a/45276657/506147
public GetQuoteResponse getQuote(String ticker) {
GetQuote request = new GetQuote();
request.setSymbol(ticker);
log.info("Requesting quote for " + ticker);
GetQuoteResponse response = (GetQuoteResponse) getWebServiceTemplate()
.marshalSendAndReceive("http://www.webservicex.com/stockquote.asmx",
request,
new SoapActionCallback("http://www.webserviceX.NET/GetQuote"));
return response;
}
Code test:
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 |
答案 18 :(得分:0)
原始值类型或结构类型的每个变量或字段都包含该类型的唯一实例,包括其所有字段(公共和私有)。相反,引用类型的变量或字段可以保持为空,或者可以指代存储在别处的对象,也可以存在任何数量的其他引用。 struct的字段将存储在与该结构类型的变量或字段相同的位置,该变量或字段可以在堆栈上,也可以是 另一个堆对象的一部分。
创建原始值类型的变量或字段将使用默认值创建它;创建结构类型的变量或字段将创建一个新实例,以默认方式在其中创建所有字段。创建引用类型的新实例将首先以默认方式创建其中的所有字段,然后根据类型运行可选的附加代码。
将基本类型的一个变量或字段复制到另一个变量或字段将复制该值。将一个变量或结构类型的字段复制到另一个变量或字段会将前一个实例的所有字段(公共和私有)复制到后一个实例。将一个变量或引用类型字段复制到另一个变量或字段将导致后者引用与前者相同的实例(如果有)。
值得注意的是,在某些语言(如C ++)中,类型的语义行为与其存储方式无关,但对于.NET则不然。如果一个类型实现了可变值语义,那么将该类型的一个变量复制到另一个变量将第一个的属性复制到另一个实例,由第二个实例引用,并使用第二个的成员进行变异,这将导致第二个实例被更改,但不是第一个。如果一个类型实现了可变引用语义,将一个变量复制到另一个变量并使用第二个变量成员来改变该对象将影响第一个变量引用的对象;具有不可变语义的类型不允许变异,因此从语义上讲,复制是创建新实例还是创建对第一个实例的另一个引用无关紧要。
在.NET中,值类型可以实现上述任何语义,前提是它们的所有字段都可以这样做。但是,引用类型只能实现可变引用语义或不可变语义;具有可变引用类型字段的值类型仅限于实现可变引用语义或奇怪的混合语义。