在 C# 中,是否可以有一个大数组,然后是指向较大数组子部分的较小数组
本文关键字:数组 小数 然后 是否 有一个 | 更新日期: 2023-09-27 18:37:27
我想有一个数组,它将我类的所有参数保存在一个数组中,然后让每个参数指向它们在大数组中的位置。
我想这样做的原因是因为我将优化基于所述参数的功能,但最小化要求所有参数都在单个数组中 - 我不想在每一步来回复制这个数组 - 我宁愿让各个参数指向它们在主数组中的位置。像这样:
class myClass{
int nVariables;
public double[] parameters; //length = X * nVarialbes
public double[] A; //length = X
public double[] B; //length = X
public double[] C; //length = X
}
其中 parameters
实际上类似于 [a1,a2,a3,b1,b2,b3,c1,c2,c3]
(在本例中X==3
)——每个参数的数量在这里是相同的,但我宁愿使用允许它是通用的解决方案。
这也将允许我更改A
、B
和C
的元素,并让保存它们的数组也都更新。
我需要让数组将所有数据保存为一个连续数组以进行优化,所以只做parameters = new double[]{A,B,C};
是行不通的 - 除非有一种方法可以像单个数组一样访问它; 我的理解是,锯齿状数组在内存中不是连续的,而且我对矩形数组不够熟悉,无法在这里做我想做的事情。
这在 .NET 中是不可能的。数组不是指针。
为此,您必须使用数组以外的类型。如果要引用数组的子部分,可以使用ArraySegment<T>
类型。您还可以创建一个包装数组部分的自定义IList<T>
。
也许答案会变成没有实用的方法来实现你想要的。我相信正在做一些工作来向 .NET 添加"切片",这些切片能够以您想要的方式引用内存范围。这项工作正在corefxlab存储库中的Github上完成。
这对于传统数组是不可能的。 .NET/CIL 中的数组以 8 字节标头开头,该标头提供虚拟表和数组长度。 由于 8 字节标头,在另一个数组中声明数组是不可行的。
但是,这可以通过指针来完成。 参加以下课程:
unsafe class MyClass : IDisposable
{
double[] parameters = new double[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
private double* p;
public double* A;
public double* B;
public double* C;
GCHandle _handle;
public MyClass()
{
_handle = GCHandle.Alloc(parameters, GCHandleType.Pinned);
p = (double*)_handle.AddrOfPinnedObject().ToPointer();
A = p;
B = (p + 3);
C = (p + 6);
}
public override string ToString()
{
return String.Join(", ", parameters.Select(a=>a.ToString()));
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
_handle.Free();
disposedValue = true;
}
}
~MyClass() {
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
构造函数创建一个指向 parameters
的固定指针,以防止垃圾回收器在内存中移动它。 指针 p
是 parameters
的第一个元素的地址。 该类公开指向数组的第 0、3 和 6 个元素的三个指针(A、B 和 C)。
虽然指针不是数组,但它们的引用类似于 C 数组。 作为一个简单的驱动程序:
unsafe static void Main(string[] args)
{
MyClass mc = new myClass();
Console.WriteLine(mc.ToString()); // prints 1, 2, 3, 4, 5, 6, 7, 8, 9
mc.B[2] = 42; // now parameter[5] set to 42
Console.WriteLine(mc.ToString()); // prints 1, 2, 3, 4, 5, 42, 7, 8, 9
}
请注意,此方法不适用于 C#,在 C# 中,通常为专用方案保留的不安全代码很少需要。 使用ArraySegment<T>
或建议的其他方法更为传统。
你能不能有一个简单的帮助函数来接受主数组、函数偏移量、长度(用于检查目的)和他们想要的函数相对数组中的参数。
我可以看到这样的函数在工作:
public T GetFunctionParam(T[] mainArray,
int FuncOffset,
int FuncParamCount,
int targetParam)
然后,您可以在需要时简单地在函数中调用它以获取特定参数
var param = GetFunctionParam(mArray, 3, 3, 2)
但是所有类型都必须相同才能出现在数组中,或者使用继承或接口。
如果主数组中的对象是引用类型,则可以为每个函数创建单独的数组,并使用对原始对象的引用填充它。
int n = 0; //index for long array
parameters = new double[combinedLength];
double[][] arrays = new double[][]{A,B,C}; //you can do it with 3 "for" loops instead.
for(int i = 0; i < arrays.length; i++){
for( int j = 0; j < arrays[i].length; j++){
parameters[n++] = arrays[i][j];
}
}
此代码采用 3 个任意长度的数组,并将它们复制到一个长数组中。
请务必注意,这是一个副本,而不是引用,因此如果您更改一个副本,另一个将保持不变。
如果您希望一个数组影响其他数组,请将 A、B 和 C 作为包含所需变量的对象数组。相同的代码应该适用于组装数组。
对于 Objects,赋值运算符不会复制值,而是分配引用。
所以参数 [0] 与 A[0] 是同一个对象。
小心保留同一对象的多个引用,这是许多错误、内存泄漏和头痛的常见来源。
如何不创建包含主数组索引的第二个数组?
像这样的电话
paramters[A[0]]
那么你就有了价值
像这样:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var mClass = new myClass();
Console.WriteLine("Before Optimization");
mClass.PrintParameters();
mClass.Optimize();
Console.WriteLine("After Optimization");
mClass.PrintParameters();
Console.ReadKey();
}
}
class myClass
{
int nVariables;
public double[] parameters; //length = X * nVarialbes
public int[] A; //length = X
public int[] B; //length = X
public int[] C; //length = X
public myClass()
{
parameters = new[] {0.0, 1.1, 2.2, 3.3, 4.4, 5.5};
A = new[] {0, 1};
B = new[] {2, 3};
C = new[] {4, 5};
}
public void Optimize()
{
//A's need to have b's paramaters added to them
parameters[A[0]] += parameters[B[0]];
parameters[A[1]] += parameters[B[1]];
//C's paramaters get their sign inverted
parameters[C[0]] *= -1;
parameters[C[1]] *= -1;
}
public void PrintParameters()
{
for(int index =0; index < parameters.Length; ++ index)
{
Console.WriteLine("[{0}] = {1}", index, parameters[index]);
}
}
}
}
为了好玩,我将添加另一个包含不安全结构、固定数组和显式布局的选项:
[StructLayout( LayoutKind.Explicit )]
unsafe struct BadSolution
{
[FieldOffset( 0 )]
public fixed double Data[3 * 3];
[FieldOffset( 8 * 0 )]
public fixed double A[3];
[FieldOffset( 8 * 3 )]
public fixed double B[3];
[FieldOffset( 8 * 6 )]
public fixed double C[3];
}
但是,这带来了许多缺点,其中一些是:
- 它
- 仅适用于结构,因此您必须非常小心如何传递它(与任何可变结构一样)。
- 它为您提供了一个非常大的结构(数组是结构的一部分,而不是引用)。
- 它是固定的 - 你必须在编译时手动进行布局。
- 他们不再是正确的
Array
。任何需要Array
的方法(如Array.Copy
或Array.Sort
都将不起作用。 - 它使用不安全的代码。
(当然4和5也适用于其他使用不安全代码的答案)
所以这样做的缺点很多,我不建议任何人走这条路。但是,这是可能的。