在 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)——每个参数的数量在这里是相同的,但我宁愿使用允许它是通用的解决方案。

这也将允许我更改ABC的元素,并让保存它们的数组也都更新。

我需要让数组将所有数据保存为一个连续数组以进行优化,所以只做parameters = new double[]{A,B,C};是行不通的 - 除非有一种方法可以像单个数组一样访问它; 我的理解是,锯齿状数组在内存中不是连续的,而且我对矩形数组不够熟悉,无法在这里做我想做的事情。

在 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 的固定指针,以防止垃圾回收器在内存中移动它。 指针 pparameters 的第一个元素的地址。 该类公开指向数组的第 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];
}

但是,这带来了许多缺点,其中一些是:

  1. 仅适用于结构,因此您必须非常小心如何传递它(与任何可变结构一样)。
  2. 它为您提供了一个非常大的结构(数组是结构的一部分,而不是引用)。
  3. 它是固定的 - 你必须在编译时手动进行布局。
  4. 他们不再是正确的Array。任何需要Array的方法(如Array.CopyArray.Sort都将不起作用。
  5. 它使用不安全的代码。

(当然4和5也适用于其他使用不安全代码的答案)

所以这样做的缺点很多,我不建议任何人走这条路。但是,这是可能的。