【问题标题】:How do i create a struct in C# that contains an array but does not use heap?如何在 C# 中创建一个包含数组但不使用堆的结构?
【发布时间】: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% 的时间不需要担心的实现细节。
  • 恕我直言,如果你解释你想做什么,你可能会得到更好的答案。你的问题是关于如何做到这一点。请edit您的问题。请记住,C# / Roslyn 编译器技术在优化代码和处理数据结构方面做得非常出色。如果您需要智取它,您可能确切地知道它对您做错了什么,您应该告诉我们。过早的优化会使代码完全无法维护。

标签: c# memory struct heap-memory


【解决方案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 的目的是将所有数据放在一起。
      猜你喜欢
      • 1970-01-01
      • 2021-08-05
      • 1970-01-01
      • 1970-01-01
      • 2018-05-14
      • 2012-07-30
      • 2012-05-15
      • 2012-06-10
      • 1970-01-01
      相关资源
      最近更新 更多