何时使用结构?
什么时候应该在C#中使用struct而不是类? 我的概念模型是,当项目仅仅是一个值types的集合的时候,使用结构体。 从逻辑上把它们连在一起成为一个有凝聚力的整体。
我在这里遇到了这些规则:
- 结构应该代表一个单一的值。
- 一个结构应该有一个小于16字节的内存占用。
- 创build后不应更改结构。
这些规则是否有效? 什么是一个结构意味着语义?
OP引用的源码有一些可信度…但是微软呢 – 结构使用的立场是什么? 我向微软寻求了一些额外的学习 ,下面是我发现的:
考虑定义一个结构而不是一个类,如果这个types的实例很小,而且通常是短暂的,或者通常embedded到其他对象中。
除非types具有以下所有特征,否则不要定义结构:
- 它在逻辑上表示一个单一的值,类似于基本types(整数,双精度等等)。
- 它的实例大小小于16字节。
- 它是不可改变的。
- 它不会经常被装箱。
微软一贯违反这些规则
好的,#2和#3无论如何。 我们心爱的字典有两个内部结构:
[StructLayout(LayoutKind.Sequential)] // default for structs private struct Entry //<Tkey, TValue> { // View code at *Reference Source } [Serializable, StructLayout(LayoutKind.Sequential)] public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, IDictionaryEnumerator, IEnumerator { // View code at *Reference Source }
* 参考源
“JonnyCantCode.com”的来源得到了4分之3 – 相当可原谅,因为#4可能不会是一个问题。 如果你发现自己装一个结构,重新考虑你的架构。
我们来看看为什么微软会使用这些结构:
- 每个结构,
Entry
和Enumerator
代表单个值。 - 速度
-
Entry
永远不会作为Dictionary类之外的parameter passing。 进一步的调查显示,为了满足IEnumerable的实现,Dictionary使用Enumerator
结构,它在每次请求枚举器时都会被复制。 - 在Dictionary类的内部。
Enumerator
是公开的,因为Dictionary是可枚举的,并且必须具有对IEnumerator接口实现的平等可访问性 – 例如IEnumerator getter。
更新 – 另外,当一个结构像Enumerator一样实现一个接口并且被转换为实现types时,结构变成了一个引用types并被移动到堆中。 在Dictionary类的内部,Enumerator仍然是一个值types。 但是,只要方法调用GetEnumerator()
,就会返回一个引用types的IEnumerator
。
我们在这里没有看到任何企图或certificate需要保持结构不变或保持实例大小只有16个字节或更less:
- 上面的结构中没有任何内容是
readonly
– 不是不可变的 - 这些结构的大小可以超过16个字节
-
Entry
具有未确定的生命周期(从Add()
到Remove()
,Clear()
或垃圾收集)。
而且… 4.这两个结构都存储了TKey和TValue,我们都知道它们很有参考types的能力(增加奖金信息)
尽pipe有散列键,字典也是快速的,因为实例化一个结构比引用types快。 在这里,我有一个Dictionary<int, int>
,它存储了300,000个随机递增键的随机整数。
容量:312874
MemSize:2660827字节
完成resize:5ms
总共填写时间:889ms
容量 :内部数组必须resize之前可用的元素数量。
MemSize :通过将字典序列化为MemoryStream并获取字节长度(足够精确,用于我们的目的)来确定。
完成resize :将内部数组从150862个元素调整为312874个元素所需的时间。 当你发现每个元素通过Array.CopyTo()
被顺序地拷贝时,这不是太简单。
总填充时间 :由于日志logging和我添加到源的OnResize
事件, OnResize
倾斜的; 然而,在操作过程中调整15次的同时,仍然能够填充30万个整数。 只是出于好奇,如果我已经知道这个容量,那么总共需要多长时间呢? 13毫秒
那么,现在呢,如果Entry
是一个class呢? 这些时间或指标真的会有所不同吗?
容量:312874
MemSize:2660827字节
完成resize:26ms
总共需要964ms
显然,差别在于resize。 如果字典使用容量进行初始化,有什么区别? 不够关心… 12ms 。
会发生什么是因为Entry
是一个结构,它不需要像引用types一样的初始化。 这既是价值型的美丽又是祸害。 为了使用Entry
作为引用types,我不得不插入下面的代码:
/* * Added to satisfy initialization of entry elements -- * this is where the extra time is spent resizing the Entry array * **/ for (int i = 0 ; i < prime ; i++) { destinationArray[i] = new Entry( ); } /* *********************************************** */
我必须初始化Entry
每个数组元素作为参考types的原因可以在MSDN:结构devise中find 。 简而言之:
不要为结构提供默认构造函数。
如果一个结构定义了一个默认的构造函数,那么当结构的数组被创build时,公共语言运行时会自动执行每个数组元素的默认构造函数。
某些编译器(如C#编译器)不允许结构具有默认构造函数。
其实很简单,我们将从阿西莫夫的“机器人三定律”
- 该结构必须安全使用
- 结构必须有效地执行它的function,除非这会违反规则#1
- 该结构在使用过程中必须保持完整,除非其破坏需要满足规则#1
… 我们从中拿走了什么 :简而言之,要对价值types的使用负责。 他们是快速和有效的,但有能力引起许多意想不到的行为,如果不妥善维护(即无意的副本)。
每当你不需要多态时,需要值语义,并且希望避免堆分配和相关的垃圾回收开销。 但是,需要注意的是,结构(任意大)比类参考(通常是一个机器词)更昂贵,所以类在实践中最终可能会更快。
我不同意原帖中的规定。 这是我的规则:
1)存储在数组中时,可以使用结构来实现性能。 (另请参见何时结构答案? )
2)你需要在代码中传递结构化数据到/从C / C ++
3)除非你需要,否则不要使用结构:
- 它们的行为与任务中的“正常对象”( 参考types )有所不同,当它们作为parameter passing时,会导致意想不到的行为; 如果查看代码的人不知道他们正在处理一个结构,这是特别危险的。
- 他们不能被遗传。
- 以结构作为parameter passing比类更昂贵。
当你需要值语义而不是引用语义时,使用一个结构。
编辑
不知道为什么大家都在低估这一点,但这是一个有效的观点,并且是在运algorithm则澄清了他的问题之前作出的,这是一个结构的最根本的基本原因。
如果你需要引用语义,你需要一个类而不是一个结构。
除了“这是一个价值”的答案,使用结构的一个特定场景是当你知道你有一组数据导致垃圾收集问题,并且你有很多的对象。 例如,Person实例的大型列表/数组。 这里的自然比喻是一个阶级,但是如果你有大量的长寿Person实例,他们可能会堵塞GEN-2并导致GC失速。 如果场景保证,这里一个可能的方法是使用Person 结构的数组(不是列表),即Person[]
。 现在,不是在GEN-2中有数百万个对象,而是在LOH上有一个单独的块(我假设这里没有string等,即没有任何引用的纯数值)。 这对GC的影响非常小。
处理这些数据很尴尬,因为数据可能超过了结构的大小,而且你不想一直复制fat值。 但是,直接在数组中访问它不会复制结构 – 它是就地的(与列表索引器相反,它是复制的)。 这意味着大量的索引工作:
int index = ... int id = peopleArray[index].Id;
请注意,保持值本身不变将有助于这里。 对于更复杂的逻辑,请使用带by-ref参数的方法:
void Foo(ref Person person) {...} ... Foo(ref peopleArray[index]);
再次,这是在原地 – 我们没有复制价值。
在非常具体的情况下,这个策略可以非常成功; 然而,这是一个相当先进的scernario只有当你知道你在做什么,为什么应该尝试。 这里的默认值是一个类。
来自C#语言规范 :
1.7结构
像类一样,结构是可以包含数据成员和函数成员的数据结构,但与类不同,结构是值types,不需要堆分配。 结构types的variables直接存储结构的数据,而类types的variables存储对dynamic分配对象的引用。 结构types不支持用户指定的inheritance,并且所有结构types都隐式地inheritance于types对象。
结构对于具有值语义的小型数据结构特别有用。 坐标系中的复数,点或字典中的键 – 值对都是很好的例子。 对小数据结构使用结构而不是类可以使应用程序执行的内存分配数量发生很大的变化。 例如,下面的程序创build并初始化一个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构造函数,但这并不意味着正在分配内存。 结构构造函数并不是dynamic分配一个对象并返回一个引用,而是直接返回结构值本身(通常在栈上的临时位置),然后根据需要复制该值。
对于类,可能有两个variables引用同一个对象,因此可能对一个variables的操作影响另一个variables引用的对象。 使用结构体时,variables都有自己的数据副本,而一个variables不能影响另一个variables。 例如,下面的代码片断产生的输出取决于Point是一个类还是一个结构体。
Point a = new Point(10, 10); Point b = a; ax = 20; Console.WriteLine(bx);
如果Point是一个类,则输出为20,因为a和b引用相同的对象。 如果Point是一个结构,则输出为10,因为a到b的赋值创build了该值的副本,并且该副本不受后续赋值的影响
前面的例子突出了结构的两个限制。 首先,复制整个结构的效率通常低于复制对象引用的效率,所以赋值和值parameter passing对于结构而言可能比引用types更昂贵。 其次,除了ref和out参数之外,不可能创build对结构体的引用,这会在很多情况下排除它们的用法。
结构对于数据的primefaces表示是有利的,其中所述数据可以被代码复制多次。 克隆一个对象通常比复制一个结构更昂贵,因为它涉及到分配内存,运行构造函数和释放/垃圾回收。
这是一个基本的规则。
-
如果所有的成员字段是值types创build一个结构 。
-
如果任何一个成员字段是引用types,则创build一个类 。 这是因为引用types字段无论如何都需要堆分配。
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 }
首先:Interop场景或需要指定内存布局时
第二:当数据几乎与参考指针的大小相同时,
我使用结构来打包或解包任何types的二进制通信格式。 这包括读取或写入磁盘,DirectX顶点列表,networking协议或处理encryption/压缩数据。
你列出的三条准则在这方面对我没有用处。 当我需要以特定顺序写出四百个字节的东西时,我将定义一个四百字节的结构体,并且将填充它应该有的任何不相关的值,然后我要去无论什么方式最有意义的时候设置它。 (好吧,四百个字节将是非常奇怪的 – 但是当我写Excel文件为生时,我正在处理大约四十个字节的结构,因为这是BIFFlogging有多大。
在需要使用StructLayoutAttribute (通常用于PInvoke)显式指定内存布局的情况下,您需要使用“struct”。
编辑:评论指出,你可以使用StructLayoutAttribute的类或结构,这当然是真的。 在实践中,你通常会使用一个结构 – 它被分配在堆栈上,而堆是有意义的,如果你只是传递一个非托pipe方法调用的参数。
除了由运行时直接使用的值types和用于PInvoke目的的其他值之外,您应该只在两种情况下使用值types。
- 当你需要复制语义。
- 当你需要自动初始化时,通常在这些types的数组中。
.NET支持value types
和reference types
(在Java中,您只能定义引用types)。 reference types
实例在托pipe堆中分配,并在没有未完成的引用时进行垃圾回收。 另一方面, value types
实例被分配在stack
,因此,一旦范围结束,分配的内存就会被回收。 当然, value types
通过价值传递的, reference types
通过参考的。 所有C#基本数据types(System.String除外)都是值types。
何时使用struct over class,
在C#中, structs
是value types
,类是reference types
。 您可以使用enum
关键字和struct
关键字在C#中创build值types。 使用value type
而不是reference type
会导致托pipe堆上的对象数量减less,从而导致垃圾回收器(GC)的负载较低,GC周期较less,因此性能更佳。 但是, value types
也有其不足之处。 传递一个大的struct
肯定比传递引用昂贵,这是一个明显的问题。 另一个问题是与boxing/unboxing
相关的开销。 如果你想知道什么boxing/unboxing
意思,请按照这些链接boxing
和unboxing
一个很好的解释。 除了性能之外,有时候你只需要types来实现价值语义,如果reference types
是你所拥有的,那么实现就会非常困难(或难看)。 您只应使用value types
,当您需要复制语义或需要自动初始化时,通常在这些types的arrays
中。
C#或其他.net语言中的结构types通常用于保存应该像固定大小的值组一样的事物。 结构types的一个有用的方面是结构types实例的字段可以通过修改它所保存的存储位置来修改,而不是以其他方式。 可以通过这样的方式对结构进行编码,使得对任何字段进行变异的唯一方法是构造一个全新的实例,然后使用结构体赋值通过用新实例中的值覆盖目标的所有字段来对其进行变异,但是unless a struct provides no means of creating an instance where its fields have non-default values, all of its fields will be mutable if and if the struct itself is stored in a mutable location.
Note that it's possible to design a structure type so that it will essentially behave like a class type, if the structure contains a private class-type field, and redirects its own members to that of the wrapped class object. For example, a PersonCollection
might offer properties SortedByName
and SortedById
, both of which hold an "immutable" reference to a PersonCollection
(set in their constructor) and implement GetEnumerator
by calling either creator.GetNameSortedEnumerator
or creator.GetIdSortedEnumerator
. Such structs would behave much like a reference to a PersonCollection
, except that their GetEnumerator
methods would be bound to different methods in the PersonCollection
. One could also have a structure wrap a portion of an array (eg one could define an ArrayRange<T>
structure which would hold a T[]
called Arr
, an int Offset
, and an int Length
, with an indexed property which, for an index idx
in the range 0 to Length-1
, would access Arr[idx+Offset]
). Unfortunately, if foo
is a read-only instance of such a structure, current compiler versions won't allow operations like foo[3]+=4;
because they have no way to determine whether such operations would attempt to write to fields of foo
.
It's also possible to design a structure to behave a like a value type which holds a variable-sized collection (which will appear to be copied whenever the struct is) but the only way to make that work is to ensure that no object to which the struct holds a reference will ever be exposed to anything which might mutate it. For example, one could have an array-like struct which holds a private array, and whose indexed "put" method creates a new array whose content is like that of the original except for one changed element. Unfortunately, it can be somewhat difficult to make such structs perform efficiently. While there are times that struct semantics can be convenient (eg being able to pass an array-like collection to a routine, with the caller and callee both knowing that outside code won't modify the collection, may be better than requiring both caller and callee to defensively copy any data they're given), the requirement that class references point to objects that will never be mutated is often a pretty severe constraint.
Nah – I don't entirely agree with the rules. They are good guidelines to consider with performance and standardization, but not in light of the possibilities.
As you can see in the responses, there are a log of creative ways to use them. So, these guidelines need to just be that, always for the sake of performance and efficiency.
In this case, I use classes to represent real world objects in their larger form, I use structs to represent smaller objects that have more exact uses. The way you said it, "a more cohesive whole." The keyword being cohesive. The classes will be more object oriented elements, while structs can have some of those characteristics, their on a smaller scale. IMO.
I use them a lot putting in Treeview and Listview tags where common static attributes can be accessed very quickly. I would struggle to get this info another way. For example, in my database applications, I use a Treeview where I have Tables, SPs, Functions, or any other objects. I create and populate my struct, put it in the tag, pull it out, get the data of the selection and so forth. I wouldn't do this with a class!
I do try and keep them small, use them in single instance situations, and keep them from changing. It's prudent to be aware of memory, allocation, and performance. And testing is so necessary.
I think a good first approximation is "never".
I think a good second approximation is "never".
If you are desperate for perf, consider them, but then always measure.
A class is a reference type. When an object of the class is created, the variable to which the object is assigned holds only a reference to that memory. When the object reference is assigned to a new variable, the new variable refers to the original object. Changes made through one variable are reflected in the other variable because they both refer to the same data. A struct is a value type. When a struct is created, the variable to which the struct is assigned holds the struct's actual data. When the struct is assigned to a new variable, it is copied. The new variable and the original variable therefore contain two separate copies of the same data. Changes made to one copy do not affect the other copy. In general, classes are used to model more complex behavior, or data that is intended to be modified after a class object is created. Structs are best suited for small data structures that contain primarily data that is not intended to be modified after the struct is created.
Classes and Structs (C# Programming Guide)
My rule is
1, Always use class;
2, If there is any performance issue, I try to change some class to struct depending on the rules which @IAbstract mentioned, and then do a test to see if these changes can improve performance.
I was just dealing with Windows Communication Foundation [WCF] Named Pipe and I did notice that it does make sense to use Structs in order to ensure that exchange of data is of value type instead of reference type .
A struct is a value type. If you assign a struct to a new variable, the new variable will contain a copy of the original.
public struct IntStruct { public int Value {get; set;} }
Excecution of the following results in 5 instances of the struct stored in memory:
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.
A class is a reference type. When you assign a class to a new variable, the variable contains a reference to the original class object.
public class IntClass { public int Value {get; set;} }
Excecution of the following results in only one instance of the class object in memory.
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
Struct s may increase the likelihood of a code mistake. If a value object is treated like a mutable reference object, a developer may be surprised when changes made are unexpectedly lost.
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
Struct can be used to improve garbage collection performance. While you usually don't have to worry about GC performance, there are scenarios where it can be a killer. Like large caches in low latency applications. See this post for an example:
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
I rarely use a struct for things. But that's just me. It depends whether I need the object to be nullable or not.
As stated in other answers, I use classes for real-world objects. I also have the mindset of structs are used for storing small amounts of data.
Structure or value types can be used in following scenarios –
- If you want to prevent the object to be collected by garbage collection.
- If it is a simple type and no member function modifies its instance fields
- If there is no need to derive from other types or being derived to other types.
You can know more about the value types and values types here on this link
Briefly, use struct if :
1- your object properties/fields do not need to be changed. I mean you just want to give them an initial value and then read them.
2- properties and fields in your object are value type and they are not so large.
If that's the case you can take advantage of structs for a better performance and optimized memory allocation as they use only stacks rather than both stacks and heaps (in classes)
I did a small benchmark with BenchmarkDotNet to get a better understanding of "struct" benefit in numbers. I'm testing looping through array (or list) of structs (or classes). Creating those arrays or lists is out of the benchmark's scope – it is clear that "class" is more heavy will utilize more memory, and will involve GC.
So the conclusion is: be careful with LINQ and hidden structs boxing/unboxing and using structs for microoptimizations strictly stay with arrays.
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; } }
The C# struct is a lightweight alternative to a class. It can do almost the same as a class, but it's less "expensive" to use a struct rather than a class. The reason for this is a bit technical, but to sum up, new instances of a class is placed on the heap, where newly instantiated structs are placed on the stack. Furthermore, you are not dealing with references to structs, like with classes, but instead you are working directly with the struct instance. This also means that when you pass a struct to a function, it is by value, instead of as a reference. There is more about this in the chapter about function parameters.
So, you should use structs when you wish to represent more simple data structures, and especially if you know that you will be instantiating lots of them. There are lots of examples in the .NET framework, where Microsoft has used structs instead of classes, for instance the Point, Rectangle and Color struct.
It seems to me that struct have no strong semantic that give the user a strong idea of when to use it.
It resemble as a class but lake most of its feature. It is a kind of degraded version of a class. There is a lot of says about when not use it but very few on when to use it.
IMO, there is no reason why struct should be implemented in a OO language at the first place. Actually primitive type should not exist in a pure OO language but I digress.
It might be a way to optimize stuff. A kind of boxing free things that can be optimize on some call site.
My 2 cent, I would say that it violated the KISS of the language principle and avoid it as much has I can.
Structures are in most ways like classes/objects. Structure can contain functions, members and can be inherited. But structures are in C# used just for data holding . Structures does take less RAM than classes and are easier for garbage collector to collect . But when you use functions in your structure, then compiler actually takes that structure very similarly as class/object, so if you want something with functions, then use class/object .