什么时候应该在C#中使用struct而不是class?我的概念模型是当项目仅仅是值类型的集合时,使用结构体。一种逻辑上将它们组合在一起形成一个有凝聚力的整体的方法。
我遇到了这些规则here:
这些规则有效吗?结构在语义上意味着什么?
答案 0 :(得分:577)
答案 1 :(得分:151)
每当您不需要多态时,需要值语义,并希望避免堆分配和相关的垃圾收集开销。然而,需要注意的是,结构(任意大)传递比类引用(通常是一个机器字)更昂贵,因此类在实践中最终会更快。
答案 2 :(得分:141)
我不同意原帖中的规定。这是我的规则:
1)存储在数组中时使用结构体来提高性能。 (另见When are structs the answer?)
2)您需要将代码传递给C / C ++的结构化数据
3)除非你需要,否则不要使用结构:
答案 3 :(得分:83)
当您需要值语义而不是引用语义时,请使用结构。
不确定为什么人们会贬低这一点,但这是一个有效的观点,并且在操作澄清他的问题之前做出,这是结构的最基本的基本原因。
如果需要引用语义,则需要一个类而不是结构。
答案 4 :(得分:58)
除了“它是一个值”的答案之外,使用结构的一个特定方案是,当您 知道 时,您有一组导致垃圾的数据收集问题,你有很多对象。例如,Person实例的大型列表/数组。这里的自然隐喻是一个类,但是如果你有大量长寿的Person实例,它们最终会堵塞GEN-2并导致GC停顿。如果场景保证,这里的一种可能方法是使用Person structs 的数组(不是列表),即Person[]
。现在,不是在GEN-2中拥有数百万个对象,而是在LOH上有一个块(我假设这里没有字符串等 - 即没有任何引用的纯值)。这对GC影响很小。
使用这些数据很尴尬,因为数据可能超出了结构的大小,并且您不希望一直复制胖值。但是,直接在数组中访问它不会复制结构 - 它就位(与列表索引器相比,它会复制)。这意味着很多工作与索引:
int index = ...
int id = peopleArray[index].Id;
请注意,保持值本身不可变将有助于此处。对于更复杂的逻辑,请使用带有by-ref参数的方法:
void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);
同样,这是就地 - 我们没有复制价值。
在非常具体的情况下,这种策略可以非常成功;但是,只有当你知道自己在做什么以及为什么这样做时,它才是一个相当先进的scernario。这里的默认值是一个类。
答案 5 :(得分:39)
1.7结构
与类一样,结构体是可以包含数据成员和函数成员的数据结构,但与类不同,结构体是 值类型,不需要堆分配。结构的变量 type直接存储struct的数据,而a的变量 class类型存储对动态分配的对象的引用。 结构类型不支持用户指定的继承和所有结构 类型隐式继承自类型对象。
结构对于具有的小型数据结构特别有用 价值语义。复数,坐标系中的点,或 字典中的键值对都是结构的好例子。该 对于小型数据结构,可以使用结构而不是类 应用程序的内存分配数量差异很大 施行。例如,以下程序创建并初始化 一个100分的阵列。将Point实现为一个类,101 单独的对象被实例化 - 一个用于数组,一个用于数组 100个元素。
class Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Test
{
static void Main() {
Point[] points = new Point[100];
for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
}
}
另一种方法是使Point成为结构。
struct Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
现在,只实例化了一个对象 - 数组的对象 - 并且Point实例以串联方式存储在数组中。
使用new运算符调用struct构造函数,但这并不意味着正在分配内存。结构构造函数不是动态分配对象并返回对它的引用,而是简单地返回结构值本身(通常在堆栈上的临时位置),然后根据需要复制该值。
对于类,两个变量可能引用同一个对象,因此对一个变量的操作可能会影响另一个变量引用的对象。对于结构体,每个变量都有自己的数据副本,并且一个变量不可能影响另一个变量。例如,由以下代码片段生成的输出取决于Point是类还是结构。
Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);
如果Point是一个类,则输出为20,因为a和b引用同一个对象。如果Point是结构,则输出为10,因为a到b的赋值会创建值的副本,并且此副本不受后续分配给a.x的影响。
前面的例子强调了结构的两个限制。首先,复制整个结构通常比复制对象引用效率低,因此对于结构而言,赋值和值参数传递可能比使用引用类型更昂贵。其次,除了ref和out参数之外,不可能创建对结构的引用,这会在很多情况下排除它们的使用。
答案 6 :(得分:33)
结构适用于数据的原子表示,其中所述数据可以通过代码多次复制。克隆一个对象通常比复制一个结构更昂贵,因为它涉及分配内存,运行构造函数和完成它时解除分配/垃圾回收。
答案 7 :(得分:25)
这是一条基本规则。
如果所有成员字段都是值类型,请创建结构。
如果任何一个成员字段是引用类型,请创建类。这是因为引用类型字段无论如何都需要堆分配。
<强> Exmaples 强>
public struct MyPoint
{
public int X; // Value Type
public int Y; // Value Type
}
public class MyPointWithName
{
public int X; // Value Type
public int Y; // Value Type
public string Name; // Reference Type
}
答案 8 :(得分:18)
首先:互操作场景或需要指定内存布局时
第二:当数据与参考指针几乎相同时。
答案 9 :(得分:17)
在您希望使用StructLayoutAttribute显式指定内存布局的情况下,您需要使用“struct” - 通常用于PInvoke。
编辑:注释指出您可以在StructLayoutAttribute中使用类或结构,这当然是正确的。在实践中,您通常会使用一个结构 - 它在堆栈和堆上分配,如果您只是将参数传递给非托管方法调用,这是有意义的。
答案 10 :(得分:16)
我使用结构包装或解压缩任何种类的二进制通信格式。这包括读取或写入磁盘,DirectX顶点列表,网络协议或处理加密/压缩数据。
在此上下文中,您列出的三条准则对我没用。当我需要在特定顺序中写出四百个字节的东西时,我将定义一个四百字节的结构,并且我将填充它应该具有的任何不相关的值,并且我将要去以任何最有意义的方式设置它。 (好吧,四百个字节会很奇怪 - 但是当我以Excel文件为生,我正在处理全部最多大约四十个字节的结构,因为那是BIFF记录的大小。)
答案 11 :(得分:15)
除了运行时和其他各种用于PInvoke目的的值类型之外,您应该只在两种情况下使用valuetype。
答案 12 :(得分:14)
.NET支持value types
和reference types
(在Java中,您只能定义引用类型)。 reference types
的实例在托管堆中分配,并且在没有对它们的未完成引用时进行垃圾回收。另一方面,value types
的实例在stack
中分配,因此一旦其范围结束,就会回收分配的内存。当然,value types
通过值传递,reference types
通过引用传递。除System.String外,所有C#原始数据类型都是值类型。
何时使用struct over class,
在C#中,structs
为value types
,类为reference types
。您可以使用enum
关键字和struct
关键字在C#中创建值类型。使用value type
而不是reference type
将导致托管堆上的对象更少,从而导致垃圾收集器(GC)上的负载更少,GC周期更少,从而提高性能。但是,value types
也有其缺点。绕过一个大struct
肯定比传递一个引用更昂贵,这是一个明显的问题。另一个问题是与boxing/unboxing
相关的开销。如果您想知道boxing/unboxing
的含义,请点击这些链接,以获得有关boxing
和unboxing
的详细说明。除了性能之外,有时候你只需要类型来拥有值语义,如果只有reference types
,那么实现它将非常困难(或难看)。您应该仅使用value types
,当您需要复制语义或需要自动初始化时,通常在这些类型的arrays
中。
答案 13 :(得分:10)
C#或其他.net语言中的结构类型通常用于保存应该像固定大小的值组一样的事物。结构类型的一个有用方面是结构类型实例的字段可以通过修改其所在的存储位置来修改,而不是以其他方式。可以以这样的方式对结构进行编码:改变任何字段的唯一方法是构造一个全新的实例,然后使用结构赋值通过用新实例中的值覆盖它们来改变目标的所有字段,但是除非struct没有提供创建其字段具有非默认值的实例的方法,否则如果struct本身存储在可变位置,则其所有字段都是可变的。
请注意,如果结构包含私有类类型字段,并且将自己的成员重定向到包装类对象的成员,则可以设计一种结构类型,使其基本上表现得像类类型。例如,PersonCollection
可能会提供属性SortedByName
和SortedById
,这两个属性都包含对PersonCollection
的“不可变”引用(在其构造函数中设置)并实现{{} 1}}通过调用GetEnumerator
或creator.GetNameSortedEnumerator
。此类结构的行为与creator.GetIdSortedEnumerator
的引用非常相似,只是它们的PersonCollection
方法将绑定到GetEnumerator
中的不同方法。也可以有一个结构包裹一个数组的一部分(例如,一个可以定义一个PersonCollection
结构,它将保存一个名为ArrayRange<T>
的{{1}},一个int T[]
,和一个int Arr
,带有索引属性,对于0到Offset
范围内的索引Length
,将访问idx
)。不幸的是,如果Length-1
是这种结构的只读实例,当前的编译器版本将不允许像Arr[idx+Offset]
这样的操作,因为它们无法确定此类操作是否会尝试写入foo
。
也可以设计一个结构,使其表现得像一个值类型,它保存一个可变大小的集合(每当结构都会被复制时),但唯一的方法就是确保没有对象结构持有引用的对象将暴露于任何可能使其变异的东西。例如,可以有一个类似于数组的结构,它包含一个私有数组,其索引的“put”方法创建一个新数组,其内容与原始数组的内容类似,除了一个更改的元素。不幸的是,使这种结构有效地执行可能有些困难。虽然有时结构语义可以很方便(例如,能够将类似数组的集合传递给例程,而调用者和被调用者都知道外部代码不会修改集合,可能比要求调用者和调用者更好。 callee防御性地复制他们给出的任何数据,类引用指向永远不会变异的对象的要求通常是非常严格的约束。
答案 14 :(得分:10)
struct 是值类型。如果将结构分配给新变量,则新变量将包含原始变量的副本。
public struct IntStruct {
public int Value {get; set;}
}
以下结果导致存储在内存中的结构的 5个实例:
var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1; // A copy is made
var struct3 = struct2; // A copy is made
var struct4 = struct3; // A copy is made
var struct5 = struct4; // A copy is made
// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.
// Although structs are designed to use less system resources
// than classes. If used incorrectly, they could use significantly more.
类是引用类型。将类分配给新变量时,该变量包含对原始类对象的引用。
public class IntClass {
public int Value {get; set;}
}
以下内容导致内存中类对象的只有一个实例。
var class1 = new IntClass() { Value = 0 };
var class2 = class1; // A reference is made to class1
var class3 = class2; // A reference is made to class1
var class4 = class3; // A reference is made to class1
var class5 = class4; // A reference is made to class1
结构可能会增加代码错误的可能性。如果将值对象视为可变引用对象,那么当所做的更改意外丢失时,开发人员可能会感到惊讶。
var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when
// struct1.Value is 0 and not 1
答案 15 :(得分:9)
正如您在回复中所看到的,有很多创造性的方法可以使用它们。因此,这些指导方针只需要这样,总是为了性能和效率。
在这种情况下,我使用类来表示更大形式的真实世界对象,我使用结构来表示具有更精确用途的较小对象。你说的方式,“一个更有凝聚力的整体。”关键词具有凝聚力。这些类将是更多面向对象的元素,而结构体可以具有一些这些特征,尽管规模较小。 IMO。
我在Treeview和Listview标签中经常使用它们,可以非常快速地访问常见的静态属性。我一直在努力以另一种方式获取此信息。例如,在我的数据库应用程序中,我使用Treeview,其中包含表,SP,函数或任何其他对象。我创建并填充我的结构,将其放在标签中,将其拉出,获取选择的数据等等。我不会在上课时这样做!
我确实尝试将它们保持在小的状态,在单实例情况下使用它们,并防止它们发生变化。注意记忆,分配和性能是明智的。测试是必要的。
答案 16 :(得分:9)
我用BenchmarkDotNet做了一个小基准,以便更好地理解数字中“结构”的好处。我正在测试循环遍历结构(或类)的数组(或列表)。创建这些数组或列表超出了基准范围 - 很明显,“类”更重将会占用更多内存,并且将涉及GC。
所以结论是:小心LINQ和隐藏的结构装箱/拆箱,并使用结构进行微优化,严格保留数组。
P.S。关于通过调用堆栈传递struct / class的另一个基准是https://stackoverflow.com/a/47864451/506147
BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
Core : .NET Core 4.6.25211.01, 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
TestListClass | Clr | Clr | 5.599 us | 0.0408 us | 0.0382 us | 5.561 us | 5.689 us | 5.583 us | 3 | - | 0 B |
TestArrayClass | Clr | Clr | 2.024 us | 0.0102 us | 0.0096 us | 2.011 us | 2.043 us | 2.022 us | 2 | - | 0 B |
TestListStruct | Clr | Clr | 8.427 us | 0.1983 us | 0.2204 us | 8.101 us | 9.007 us | 8.374 us | 5 | - | 0 B |
TestArrayStruct | Clr | Clr | 1.539 us | 0.0295 us | 0.0276 us | 1.502 us | 1.577 us | 1.537 us | 1 | - | 0 B |
TestLinqClass | Clr | Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us | 7 | 0.0153 | 80 B |
TestLinqStruct | Clr | Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us | 9 | - | 96 B |
TestListClass | Core | Core | 5.747 us | 0.1147 us | 0.1275 us | 5.567 us | 5.945 us | 5.756 us | 4 | - | 0 B |
TestArrayClass | Core | Core | 2.023 us | 0.0299 us | 0.0279 us | 1.990 us | 2.069 us | 2.013 us | 2 | - | 0 B |
TestListStruct | Core | Core | 8.753 us | 0.1659 us | 0.1910 us | 8.498 us | 9.110 us | 8.670 us | 6 | - | 0 B |
TestArrayStruct | Core | Core | 1.552 us | 0.0307 us | 0.0377 us | 1.496 us | 1.618 us | 1.552 us | 1 | - | 0 B |
TestLinqClass | Core | Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us | 8 | 0.0153 | 72 B |
TestLinqStruct | Core | Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us | 10 | - | 88 B |
代码:
[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
[ClrJob, CoreJob]
[HtmlExporter, MarkdownExporter]
[MemoryDiagnoser]
public class BenchmarkRef
{
public class C1
{
public string Text1;
public string Text2;
public string Text3;
}
public struct S1
{
public string Text1;
public string Text2;
public string Text3;
}
List<C1> testListClass = new List<C1>();
List<S1> testListStruct = new List<S1>();
C1[] testArrayClass;
S1[] testArrayStruct;
public BenchmarkRef()
{
for(int i=0;i<1000;i++)
{
testListClass.Add(new C1 { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
}
testArrayClass = testListClass.ToArray();
testArrayStruct = testListStruct.ToArray();
}
[Benchmark]
public int TestListClass()
{
var x = 0;
foreach(var i in testListClass)
{
x += i.Text1.Length + i.Text3.Length;
}
return x;
}
[Benchmark]
public int TestArrayClass()
{
var x = 0;
foreach (var i in testArrayClass)
{
x += i.Text1.Length + i.Text3.Length;
}
return x;
}
[Benchmark]
public int TestListStruct()
{
var x = 0;
foreach (var i in testListStruct)
{
x += i.Text1.Length + i.Text3.Length;
}
return x;
}
[Benchmark]
public int TestArrayStruct()
{
var x = 0;
foreach (var i in testArrayStruct)
{
x += i.Text1.Length + i.Text3.Length;
}
return x;
}
[Benchmark]
public int TestLinqClass()
{
var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
return x;
}
[Benchmark]
public int TestLinqStruct()
{
var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
return x;
}
}
答案 17 :(得分:8)
我的规则是
1,始终使用课程;
2,如果存在任何性能问题,我会尝试根据@IAbstract提到的规则将某些类更改为struct,然后进行测试以查看这些更改是否可以提高性能。
答案 18 :(得分:8)
类是引用类型。创建类的对象时,为其分配对象的变量仅保留对该内存的引用。将对象引用分配给新变量时,新变量引用原始对象。通过一个变量进行的更改会反映在另一个变量中,因为它们都引用相同的数据。 结构是一种值类型。创建结构时,为其分配结构的变量保存结构的实际数据。将结构分配给新变量时,会复制该变量。因此,新变量和原始变量包含相同数据的两个单独副本。对一个副本所做的更改不会影响另一个副本。 通常,类用于建模更复杂的行为,或者在创建类对象后要修改的数据。结构最适合于小数据结构,主要包含在创建结构后不打算修改的数据。
答案 19 :(得分:5)
我刚刚处理Windows Communication Foundation [WCF] Named Pipe,我确实注意到使用Structs以确保数据交换是值类型而不是< strong>参考类型。
答案 20 :(得分:4)
我认为良好的初步近似是“从不”。
我认为好的第二个近似是“从不”。
如果你非常渴望性能,请考虑它们,但随后要测量。
答案 21 :(得分:4)
C#struct是类的轻量级替代品。它可以与类几乎相同,但使用结构而不是类不那么“昂贵”。这样做的原因有点技术性,但总而言之,类的新实例放在堆上,其中新实例化的结构放置在堆栈上。此外,您不处理对结构的引用,例如类,而是直接使用结构实例。这也意味着当您将结构传递给函数时,它是按值而不是作为引用。关于函数参数的章节中有更多相关内容。
因此,当您希望表示更简单的数据结构时,您应该使用结构,特别是如果您知道将要实例化大量数据结构。 .NET框架中有很多示例,其中Microsoft使用了结构而不是类,例如Point,Rectangle和Color结构。
答案 22 :(得分:3)
结构或值类型可用于以下场景 -
您可以详细了解值类型和值types here on this link
答案 23 :(得分:3)
简而言之,使用struct if:
1-您的对象属性/字段不需要更改。我的意思是你只想给他们一个初始值,然后阅读它们。
对象中的2-属性和字段是值类型,并且它们不是很大。
如果是这种情况,你可以利用结构来获得更好的性能和优化的内存分配,因为它们只使用堆栈而不是堆栈和堆(在类中)
答案 24 :(得分:3)
Struct可用于提高垃圾收集性能。虽然您通常不必担心GC性能,但有些情况下它可能会成为杀手。就像低延迟应用程序中的大缓存一样。有关示例,请参阅此文章:
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
答案 25 :(得分:2)
我很少使用结构来做事。但那只是我。这取决于我是否需要对象可以为空。
正如其他答案所述,我使用的是真实世界对象的类。我也有结构的心态用于存储少量数据。
答案 26 :(得分:2)
除了经常提到的性能差异之外,让我补充另一个方面,那就是揭示默认值用法的意图。
<块引用>如果结构体字段的默认值不代表建模概念的合理默认值,则不要使用结构体。
例如
如果你用一个类来实现一个概念,那么你可以强制执行某些不变量,例如。一个人必须有名字和姓氏。但是使用结构体总是可以创建一个实例,其所有字段都设置为默认值。
所以当建模一个没有合理默认值的概念时,更喜欢一个类。你的类的用户会明白 null 意味着没有指定 PersonName 但如果你给他们一个 PersonName 结构体实例并将其所有属性设置为 null,他们会感到困惑。
(通常的免责声明:性能考虑可能会覆盖此建议。如果您有性能问题,请务必在决定解决方案之前进行衡量。试试 BenchmarkDotNet 太棒了!)
答案 27 :(得分:1)
神话之一:结构是轻量级课程
这个神话有多种形式。有些人认为价值类型不能或 不应有方法或其他重大行为-应该简单 数据传输类型,仅具有公共字段或简单属性。 DateTime类型是 一个很好的反例:将其作为值类型是有意义的 基本单位(例如数字或字符),将其作为 能够根据其值进行计算。从另一个角度看事情 方向,无论如何,数据传输类型通常都应该是引用类型。 应该基于期望的值或引用类型的语义,而不是基于 方式。 其他人认为,就术语而言,值类型比参考类型“更轻” 性能。事实是,在某些情况下,值类型的性能更高- 他们不需要垃圾收集,除非将它们装箱,没有类型 识别开销,并且不需要取消引用。但在其他 方式,引用类型更有效-参数传递,将值分配给 变量,返回值和类似操作仅需要复制4或8个字节(取决于您运行的是32位还是64位CLR),而不是 复制所有数据。想象一下ArrayList是否是某种“纯”值类型,并且 将ArrayList表达式传递给涉及复制所有数据的方法!在几乎 在所有情况下,性能并不是由这种决定来决定的。瓶颈几乎永远不会出现在您认为的地方,在基于性能做出设计决策之前,您应该衡量不同的选择。 值得注意的是,两种信念的结合也不起作用。它 类型具有多少方法(无论是类还是结构)都无关紧要- 每个实例占用的内存不会受到影响。 (就内存而言,这是有代价的 占用了代码本身,但只发生了一次,而不是每个实例都发生了。)
神话2:堆上的引用类型;堆栈中存在的值类型
这通常是由重复它的人的懒惰引起的。首先 part是正确的-引用类型的实例总是在堆上创建。这是 第二部分会引起问题。正如我已经指出的,变量的值可以保存在声明的任何位置,因此,如果您的类具有实例类型为int的实例变量,则任何给定对象的该变量的值将始终位于该对象的其余数据中 在堆上。仅局部变量(方法内声明的变量)和方法 参数存在于堆栈中。在C#2和更高版本中,即使是一些局部变量也不是真正 正如第5章中讨论匿名方法时所看到的, 这些概念现在相关吗?有争议的是,如果要编写托管代码,则应让运行时担心如何最好地使用内存。 实际上,语言规范不能保证生活的一切 哪里;将来的运行时可能会在堆栈上创建一些对象 知道它可以摆脱它,或者C#编译器可以生成代码 几乎不使用堆栈。 下一个神话通常只是术语问题。
神话3:对象在C#中的引用程度是默认值
这可能是传播最广的神话。再说一次,做这个的人 声称经常(尽管并不总是)知道C#的实际行为,但他们不知道 “通过引用”的真正含义。不幸的是,这对于那些 知道这意味着什么。 引用传递的形式定义相对复杂,涉及l值 和类似的计算机科学术语,但重要的是,如果您通过 通过引用变量,您要调用的方法可以通过更改其参数值来更改调用方变量的值。现在,请记住参考值 类型变量是引用,而不是对象本身。您可以更改内容 参数引用的对象,而参数本身不会通过引用传递。例如,以下方法更改StringBuilder的内容 有问题的对象,但调用方的表达式仍将引用与 之前:
void AppendHello(StringBuilder builder)
{
builder.Append("hello");
}
调用此方法时,参数值(对StringBuilder的引用)为 按价值传递。如果您要在 方法-例如,使用语句builder = null;-不会发生更改 被呼叫者看到,与神话相反。 有趣的是,不仅神话的“引用”位不准确,而且“对象已传递”位也是如此。对象本身也不会传递 通过引用或按值。当涉及引用类型时,其中一个变量是 通过引用传递或参数(引用)的值通过值传递。 除了别的,这回答了当null为空时会发生什么的问题。 用作按值参数-如果传递对象,则将导致 问题,因为不会有物体通过!而是通过传递空引用 值与任何其他参考相同。 如果这种快速的解释使您感到困惑,那么您可能想看一下我的文章“ C#中的参数传递”(http://mng.bz/otVt),其中涉及更多内容。 详情。 并非只有这些神话。装箱和拆箱 相当多的误解,我将在下面尝试解决。
参考文献:乔恩·斯凯特(Jon Skeet)撰写的第三版C#
答案 28 :(得分:1)
以下是在Microsoft网站上定义的规则:
✔️考虑到类型的实例较小且通常为短寿命或通常嵌入在其他对象中的情况,请定义结构而不是类。
❌除非结构具有以下所有特征,否则避免定义结构:
它在逻辑上表示一个值,类似于基本类型(int,double等)。
实例大小小于16个字节。
这是一成不变的。
它不必经常装箱。
进一步reading
答案 29 :(得分:0)
✔️ 考虑结构使用
答案 30 :(得分:-11)
结构在很多方面类似于类/对象。结构可以包含函数,成员并且可以继承。但是C#中的结构仅用于数据保存。结构占用较少的RAM 比类,更容易收集垃圾收集器。但是当你在结构中使用函数时,编译器实际上将这个结构与类/对象非常相似,所以如果你需要带有函数的东西,那么使用class / object 。