SoFunction
Updated on 2025-03-07

C# + Unmanaged array example without unsafe (large unmanaged array in c# without ‘unsafe’ keyword)

Use a large array in C#

In C#, sometimes I need to be able to apply for a large array, use it, and then immediately release the memory it consumes.

Sometimes I need to allocate a large array, use it and then release its memory space immediately.

Due to the int[] array = new int[1000000]; provided in C#, the memory release of the array is difficult to be fully controlled by the programmer, and the program may become very slow after applying for a large array.

If I use something like  int[] array = new int[1000000]; , it will be difficult to release its memory space by programmer and the app probably runs slower and slower.

Especially in C# + OpenGL programming, I need to design an unmanaged array when using VAO/VBO. For example, when glBufferData, I hope to use the following glBufferData:

Specially in C#+OpenGL routines when I'm using VAO/VBO, I need an unmanaged array for glBufferData:

/// <summary>
 /// Set the data of the current VBO. /// </summary>
 /// <param name="target"></param>
 /// <param name="data"></param>
 /// <param name="usage"></param>
 public static void glBufferData(uint target, UnmanagedArrayBase data, uint usage)
 {
 GetDelegateFor<glBufferData>()((uint)target,
 , // Use unmanaged arrays , // Use unmanaged arrays (uint)usage);
 }
 // ...
 // glBufferData statement private delegate void glBufferData(uint target, int size, IntPtr data, uint usage);

When specifying VBO data, it may be float, vec3 and other types:

And the content in VBO can be float, vec3 and any other structs.

/// <summary>
 /// The position array of the pyramid. /// </summary>
 static vec3[] positions = new vec3[]
 {
 new vec3(0.0f, 1.0f, 0.0f),
 new vec3(-1.0f, -1.0f, 1.0f),
 // ...
 new vec3(-1.0f, -1.0f, 1.0f),
 };
// Create a vertex buffer for the vertex data.
 {
 uint[] ids = new uint[1];
 (1, ids);
 (GL.GL_ARRAY_BUFFER, ids[0]);
 // Use vec3 as parameter of generic unmanaged array UnmanagedArray<vec3> positionArray = new UnmanagedArray<vec3>();
 for (int i = 0; i < ; i++)
 {
  // Use this[i] to read and write elements of an unmanaged array  positionArray[i] = positions[i];
 }
 (, positionArray, );
 (positionLocation, 3, GL.GL_FLOAT, false, 0, );
 (positionLocation);
 }

UnmanagedArray<T>

So I designed such a non-managed array type: no unsafe, which can accept any struct type as a generic parameter, and can free memory at any time.

So I designed this UnmangedArray<T> : no 'unsafe' keyword, takes any struct as generic parameter, can be released anytime you want.

1 /// &lt;summary&gt;
 2 /// Unmanaged arrays with elements of sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool or other struct. 3 /// <para> cannot use enum type as T.  </para> 4 /// &lt;/summary&gt;
 5 /// <typeparam name="T">sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool or other struct, the enum type cannot be used as T.  </typeparam> 6 public class UnmanagedArray&lt;T&gt; : UnmanagedArrayBase where T : struct
 7 {
 8 
 9 /// &lt;summary&gt;
 10 ///Unmanaged arrays with element types sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool or other struct. 11 /// &lt;/summary&gt;
 12 /// &lt;param name="count"&gt;&lt;/param&gt;
 13 [MethodImpl()]
 14 public UnmanagedArray(int count)
 15 : base(count, (typeof(T)))
 16 {
 17 }
 18 
 19 /// &lt;summary&gt;
 20 /// Get or set an element indexed to <paramref name="index"/>. 21 /// &lt;/summary&gt;
 22 /// &lt;param name="index"&gt;&lt;/param&gt;
 23 /// &lt;returns&gt;&lt;/returns&gt;
 24 public T this[int index]
 25 {
 26 get
 27 {
 28  if (index &lt; 0 || index &gt;= )
 29  throw new IndexOutOfRangeException("index of UnmanagedArray is out of range");
 30 
 31  var pItem =  + (index * elementSize);
 32  //var obj = (pItem, typeof(T));
 33  //T result = (T)obj;
 34  T result = &lt;T&gt;(pItem);// works in .net 4.5.1
 35  return result;
 36 }
 37 set
 38 {
 39  if (index &lt; 0 || index &gt;= )
 40  throw new IndexOutOfRangeException("index of UnmanagedArray is out of range");
 41  
 42  var pItem =  + (index * elementSize);
 43  //(value, pItem, true);
 44  &lt;T&gt;(value, pItem, true);// works in .net 4.5.1
 45 }
 46 }
 47 
 48 /// &lt;summary&gt;
 49 /// Get each element in order in index order. 50 /// &lt;/summary&gt;
 51 /// &lt;returns&gt;&lt;/returns&gt;
 52 public IEnumerable&lt;T&gt; GetElements()
 53 {
 54 if (!)
 55 {
 56  for (int i = 0; i &lt; ; i++)
 57  {
 58  yield return this[i];
 59  }
 60 }
 61 }
 62 }
 63 
 64 /// &lt;summary&gt;
 65 /// The base class of unmanaged arrays. 66 /// &lt;/summary&gt;
 67 public abstract class UnmanagedArrayBase : IDisposable
 68 {
 69 
 70 /// &lt;summary&gt;
 71 /// Array pointer. 72 /// &lt;/summary&gt;
 73 public IntPtr Header { get; private set; }
 74 
 75 /// &lt;summary&gt;
 76 /// Number of elements. 77 /// &lt;/summary&gt;
 78 public int Count { get; private set; }
 79 
 80 /// &lt;summary&gt;
 81 /// The number of bytes of a single element. 82 /// &lt;/summary&gt;
 83 protected int elementSize;
 84 
 85 /// &lt;summary&gt;
 86 /// The number of bytes applied.  (Number of elements * Number of bytes of a single element). 87 /// &lt;/summary&gt;
 88 public int ByteLength
 89 {
 90 get { return  * ; }
 91 }
 92 
 93 
 94 /// &lt;summary&gt;
 95 /// Unmanaged array. 96 /// &lt;/summary&gt;
 97 /// <param name="elementCount">Number of elements.  </param> 98 /// <param name="elementSize">Number of bytes of a single element.  </param> 99 [MethodImpl()]
100 protected UnmanagedArrayBase(int elementCount, int elementSize)
101 {
102  = elementCount;
103  = elementSize;
104 
105 int memSize = elementCount * elementSize;
106  = (memSize);
107 
108 (this);
109 }
110 
111 private static readonly List&lt;IDisposable&gt; allocatedArrays = new List&lt;IDisposable&gt;();
112 
113 /// &lt;summary&gt;
114 /// Release all <see cref="UnmanagedArray"/> immediately.115 /// &lt;/summary&gt;
116 [MethodImpl()]
117 public static void FreeAll()
118 {
119 foreach (var item in allocatedArrays)
120 {
121  ();
122 }
123 ();
124 }
125 
126 ~UnmanagedArrayBase()
127 {
128 Dispose();
129 }
130 
131 #region IDisposable Members
132 
133 /// &lt;summary&gt;
134 /// Internal variable which checks if Dispose has already been called
135 /// &lt;/summary&gt;
136 protected Boolean disposed;
137 
138 /// &lt;summary&gt;
139 /// Releases unmanaged and - optionally - managed resources
140 /// &lt;/summary&gt;
141 /// &lt;param name="disposing"&gt;&lt;c&gt;true&lt;/c&gt; to release both managed and unmanaged resources; &lt;c&gt;false&lt;/c&gt; to release only unmanaged resources.&lt;/param&gt;
142 protected void Dispose(Boolean disposing)
143 {
144 if (disposed)
145 {
146  return;
147 }
148 
149 if (disposing)
150 {
151  //Managed cleanup code here, while managed refs still valid
152 }
153 //Unmanaged cleanup code here
154 IntPtr ptr = ;
155 
156 if (ptr != )
157 {
158   = 0;
159   = ;
160  (ptr);
161 }
162 
163 disposed = true;
164 }
165 
166 /// &lt;summary&gt;
167 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
168 /// &lt;/summary&gt;
169 public void Dispose()
170 {
171 (true);
172 (this);
173 }
174 
175 #endregion
176  
177 }

UnmanagedArray

How to use

UnmanagedArray<T> is very simple to use, just like a normal array:

Using UnamangedAray<T> is just like a normal array(int[], vec3[], etc.):

internal static void TypicalScene()
 {
 const int count = 100;

 // Test float type var floatArray = new UnmanagedArray&lt;float&gt;(count);
 for (int i = 0; i &lt; count; i++)
 {
 floatArray[i] = i;
 }
 for (int i = 0; i &lt; count; i++)
 {
 var item = floatArray[i];
 if (item != i)
 { throw new Exception(); }
 }

 // Test the int type var intArray = new UnmanagedArray&lt;int&gt;(count);
 for (int i = 0; i &lt; count; i++)
 {
 intArray[i] = i;
 }
 for (int i = 0; i &lt; count; i++)
 {
 var item = intArray[i];
 if (item != i)
 { throw new Exception(); }
 }

 // Test bool type var boolArray = new UnmanagedArray&lt;bool&gt;(count);
 for (int i = 0; i &lt; count; i++)
 {
 boolArray[i] = i % 2 == 0;
 }
 for (int i = 0; i &lt; count; i++)
 {
 var item = boolArray[i];
 if (item != (i % 2 == 0))
 { throw new Exception(); }
 }

 // Test vec3 type var vec3Array = new UnmanagedArray&lt;vec3&gt;(count);
 for (int i = 0; i &lt; count; i++)
 {
 vec3Array[i] = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2);
 }
 for (int i = 0; i &lt; count; i++)
 {
 var item = vec3Array[i];
 var old = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2);
 if ( !=  ||  !=  ||  != )
 { throw new Exception(); }
 }

 // Test foreach foreach (var item in ())
 {
 (item);
 }

 // Free the memory occupied by this array, and after that, you can no longer use vec3Array. ();

 // Immediately release all memory occupied by unmanaged arrays, and after that, you can no longer use the arrays you requested above. ();
 }

Quick read and write UnmanagedArray<T>

UnmanagedArrayHelper

Since you often need to apply for and use a large UnmanagedArray<T>, using this[index] index method directly will be slower, so I added several auxiliary methods to specifically solve the problem of fast reading and writing UnmanagedArray<T>.

public static class UnmanagedArrayHelper
 {
 ///// &lt;summary&gt;
 ////// Error 1 Unable to get the address and size of the managed type ("T"), or the pointer to it cannot be declared ///// &lt;/summary&gt;
 ///// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
 ///// &lt;param name="array"&gt;&lt;/param&gt;
 ///// &lt;returns&gt;&lt;/returns&gt;
 //public static unsafe T* FirstElement&lt;T&gt;(this UnmanagedArray&lt;T&gt; array) where T : struct
 //{
 // var header = (void*);
 // return (T*)header;
 //}

 /// &lt;summary&gt;
 /// Get the address of the first element of the unmanaged array. /// &lt;/summary&gt;
 /// &lt;param name="array"&gt;&lt;/param&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 public static unsafe void* FirstElement(this UnmanagedArrayBase array)
 {
 var header = (void*);

 return header;
 }

 public static unsafe void* LastElement(this UnmanagedArrayBase array)
 {
 var last = (void*)( + ( -  / ));

 return last;
 }

 /// &lt;summary&gt;
 /// Get the address of the last element of the unmanaged array and then the address of the next unit. /// &lt;/summary&gt;
 /// &lt;param name="array"&gt;&lt;/param&gt;
 /// &lt;returns&gt;&lt;/returns&gt;
 public static unsafe void* TailAddress(this UnmanagedArrayBase array)
 {
 var tail = (void*)( + );

 return tail;
 }
 }

How to use

This type implements 3 extension methods, which can obtain the position of the first element of UnmanagedArray<T>, the position of the last element, and the position of the last element +1. This unsafe method can achieve the same reading and writing speed in C language.

Here is an example. Reading and writing UnmanagedArray<T> in unsafe is 10 to 70 times faster than this [index] method.

public static void TypicalScene()
 {
 int length = 1000000;
 UnmanagedArray<int> array = new UnmanagedArray<int>(length);
 UnmanagedArray<int> array2 = new UnmanagedArray<int>(length);

 long tick = ;
 for (int i = 0; i < length; i++)
 {
 array[i] = i;
 }
 long totalTicks =  - tick;

 tick = ;
 unsafe
 {
 int* header = (int*)();
 int* last = (int*)();
 int* tailAddress = (int*)();
 int value = 0;
 for (int* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++)
 {
  *ptr = value++;
 }
 }
 long totalTicks2 =  - tick;
 ("ticks: {0}, {1}", totalTicks, totalTicks2);// unsafe method works faster.

 for (int i = 0; i < length; i++)
 {
 if (array[i] != i)
 {
  ("something wrong here");
 }
 if (array2[i] != i)
 {
  ("something wrong here");
 }
 }

 ();
 ();
 }
unsafe
 {
  vec3* header = (vec3*)();
  vec3* last = (vec3*)();
  vec3* tailAddress = (vec3*)();
  int i = 0;
  for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++)
  {
  *ptr = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2);
  i++;
  }
  i = 0;
  for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++, i++)
  {
  var item = *ptr;
  var old = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2);
  if ( !=  ||  !=  ||  != )
  { throw new Exception(); }
  }
 }

2015-08-25

Support complex structs with StructLayout and MarshalAs

In OpenGL I need to use UnmanagedArray<mat4>, where mat4 is defined as follows:

1 /// <summary>
 2 /// Represents a 4x4 matrix.
 3 /// </summary>
 4 [StructLayout(, CharSet = , Size = 4 * 4 * 4)]
 5 public struct mat4
 6 {
 7 /// <summary>
 8 /// Gets or sets the <see cref="vec4"/> column at the specified index.
 9 /// </summary>
10 /// <value>
11 /// The <see cref="vec4"/> column.
12 /// </value>
13 /// <param name="column">The column index.</param>
14 /// <returns>The column at index <paramref name="column"/>.</returns>
15 public vec4 this[int column]
16 {
17 get { return cols[column]; }
18 set { cols[column] = value; }
19 }
20 
21 /// <summary>
22 /// Gets or sets the element at <paramref name="column"/> and <paramref name="row"/>.
23 /// </summary>
24 /// <value>
25 /// The element at <paramref name="column"/> and <paramref name="row"/>.
26 /// </value>
27 /// <param name="column">The column index.</param>
28 /// <param name="row">The row index.</param>
29 /// <returns>
30 /// The element at <paramref name="column"/> and <paramref name="row"/>.
31 /// </returns>
32 public float this[int column, int row]
33 {
34 get { return cols[column][row]; }
35 set { cols[column][row] = value; }
36 }
37 
38 /// <summary>
39 /// The columms of the matrix.
40 /// </summary>
41 [MarshalAs(, SizeConst = 4)]
42 private vec4[] cols;
43 }
44 
45 /// <summary>
46 /// Represents a four dimensional vector.
47 /// </summary>
48 [StructLayout(, CharSet = , Size = 4 * 4)]
49 public struct vec4
50 {
51 public float x;
52 public float y;
53 public float z;
54 public float w;
55 
56 public float this[int index]
57 {
58 get
59 {
60  if (index == 0) return x;
61  else if (index == 1) return y;
62  else if (index == 2) return z;
63  else if (index == 3) return w;
64  else throw new Exception("Out of range.");
65 }
66 set
67 {
68  if (index == 0) x = value;
69  else if (index == 1) y = value;
70  else if (index == 2) z = value;
71  else if (index == 3) w = value;
72  else throw new Exception("Out of range.");
73 }
74 }
75 }

mat4

Note: The size of T must be determined for UnmanagedArray<T>. So in mat4 we use [StructLayout(, CharSet = , Size = 4 * 4 * 4)] to specify that the size of mat4 is 4 vec4 * 4 float * 4 bytes (each float) = 64 bytes, and on private vec4[] cols; we use [MarshalAs(, SizeConst = 4)] to specify that the number of elements of cols must be 4. After that, it is OK to not write [StructLayout(, CharSet = , Size = 4 * 4)] on vec4, because vec4 only has 4 simple float fields and does not contain complex types.

Below are test cases.

mat4 matrix = ((), new vec3(2, 3, 4));

 var size = (typeof(mat4));
 size = (matrix);

 UnmanagedArray<mat4> array = new UnmanagedArray<mat4>(1);
 array[0] = matrix;

 mat4 newMatirx = array[0]; // newMatrix should be equal to matrix

 ();

If matrix and newMatrix are equal, it means that the above Attribute configuration is correct.

Summarize

This is the article about C# + unmanaged array in c# without 'unsafe' keyword. For more information about C# + unmanaged array content, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!