如何在 C# 中创建一个包含数组但不使用堆的结构?
Posted
技术标签:
【中文标题】如何在 C# 中创建一个包含数组但不使用堆的结构?【英文标题】:How do i create a struct in C# that contains an array but does not use heap? 【发布时间】:2020-12-20 20:33:23 【问题描述】:我需要什么:
具有任意数量顶点(或至少达到max
顶点数量)的多边形
应该是struct,这样才能快,可以按值赋值/传递
似乎我不能使用数组或集合来存储顶点,因为那样我的多边形结构将指向堆上的对象,并且当一个多边形按值分配给另一个多边形时,只会执行浅拷贝,并且我会让两个多边形都指向同一个顶点数组。例如:
Polygon a = new Polygon();
Polygon b = a;
// both polygons would be changed
b.vertices[0] = 5;
那么我如何创建一个可以有任意数量(或一些固定数量)顶点但根本不使用堆的结构?
我可以使用很多变量,例如 v1, v2, v3 ... v10
等,但我想或多或少地保持我的代码干净。
【问题讨论】:
你真正关心的是stack vs. heap
还是reference type vs. value type
?如果是前者,请查看stackalloc。
在堆栈内存中完全拥有一个潜在的大多边形并传递它(每次将它作为函数参数传递时复制它等)不一定比使用标准更快(甚至可能更慢)收藏。您是否有需要解决的实际问题,或者您是否正在尝试先发制人地优化甚至可能不存在的问题?
我觉得在这种情况下,您的问题更多是关于深度复制列表,而不是关于堆栈与堆。 Stack 和 Heap 是我们 99% 的时间不需要担心的实现细节。
另见:.NET Collection that is a struct 和 Deep copy of List<T>
恕我直言,如果你解释你想做什么,你可能会得到更好的答案。你的问题是关于如何做到这一点。请edit您的问题。请记住,C# / Roslyn 编译器技术在优化代码和处理数据结构方面做得非常出色。如果您需要智取它,您可能确切地知道它对您做错了什么,您应该告诉我们。过早的优化会使代码完全无法维护。
【参考方案1】:
如果它符合您的逻辑,您可以使用分配在堆栈上的Span<T>
。阅读更多here
【讨论】:
【参考方案2】:另一种使用复制构造函数复制数组的方法
public Polygon(Polygon other)
this.vertices = other.vertices.Clone() as int[];
然后
var a = new Polygon();
a.vertices[0] = 5;
var b = new Polygon(a):
Debug.WriteLine(a.vertices[0]);
// 5
Debug.WriteLine(b.vertices[0]);
// 5
b.vertices[0] = 10;
Debug.WriteLine(a.vertices[0]);
// 5
Debug.WriteLine(b.vertices[0]);
// 10
【讨论】:
【参考方案3】:您可以选择使用 fixed
关键字定义您的数组,这会将其放入堆栈中。
但是你不能直接访问数组的元素,除非你在 unsafe
上下文中并且使用指针。
要获得以下行为:
static void Main(string[] args)
FixedArray vertices = new FixedArray(10);
vertices[0] = 4;
FixedArray copy = vertices;
copy[0] = 8;
Debug.WriteLine(vertices[0]);
// 4
Debug.WriteLine(copy[0]);
// 8
然后使用下面的类定义:
public unsafe struct FixedArray
public const int MaxSize = 100;
readonly int size;
fixed double data[MaxSize];
public FixedArray(int size) : this(new double[size])
public FixedArray(double[] values)
this.size = Math.Min(values.Length, MaxSize);
for (int i = 0; i < size; i++)
data[i] = values[i];
public double this[int index]
get
if (index>=0 && index<size)
return data[index];
return 0;
set
if (index>=0 && index<size)
data[index] = value;
public double[] ToArray()
var array = new double[size];
for (int i = 0; i < size; i++)
array[i] = data[i];
return array;
有几点需要考虑。以上需要用
unsafe
选项编译。MaxSize
也是一个常数,并且所需的存储空间不能超过这个值。我正在使用索引器this[int]
来访问元素(而不是字段),并且还有一种方法可以使用ToArray()
转换为本机数组。构造函数也可以采用本机数组,或者它将使用空数组来初始化值。这是为了确保new FixedArray(10)
例如将在固定数组中初始化至少 10 个值(而不是未定义,因为它是默认值)。
详细了解fixed
from Microsoft 的用法或搜索C# Fixed Size Buffers
。
堆数组字段
struct StdArray
int[] vertices;
Foo(int size)
vertices = new int[size];
堆栈数组字段
unsafe struct FixedArray
fixed int vertices[100];
int size;
Foo(int size)
this.size = size;
// no initialization needed for `vertices`
【讨论】:
修复一个对象不会将它移动到堆栈中,也不会让它表现得像一个值类型。它只是确保 GC 在其收集期间不会移动它。 @Servy 其实不是,fixed
在结构中是一个特殊的关键字,用于移动堆栈中的值。我简化了示例并删除了fixed()
语句,它仍然有效。这是自the initial introduction 以来的一项改进。试一试,你会发现这完全符合要求。
据我了解,“fixed”关键字将固定大小的数组作为结构的一部分嵌入。数组是在栈上还是堆上取决于结构体的使用方式。如果它被用作局部变量声明,那么确实,数据将存在于堆栈中,但如果它用作引用类的成员,那么缓冲区将存在于堆上(带有嵌入类的实例整个固定大小的数组作为其字段的一部分,而不是作为对独立数组的引用)
因此,如果声明了class Foo FixedArray data;
,数据可能会驻留在堆上。这是有道理的,因为struct
的目的是将所有数据放在一起。以上是关于如何在 C# 中创建一个包含数组但不使用堆的结构?的主要内容,如果未能解决你的问题,请参考以下文章