在c#列表中添加结构引用

本文关键字:结构 引用 添加 列表 | 更新日期: 2023-09-27 18:17:07

试图保持我的c#代码优化,我发现,如果我有一个结构元素列表,每次插入将是一个完整的复制-这是我想避免的。

在c++中,我会简单地保留一个指针列表,所以我想知道我是否可以使用c#做同样的事情,也许通过将列表作为对结构的引用列表。不幸的是,结构不能变成一个类,因为它是XNA库的一部分(Vector3,矩阵等…)在任何情况下——如果可能的话,格式和用法会是什么样子?

谢谢。

在c#列表中添加结构引用

基本上没有。选择:

  • 使用一个类(你已经说过你不能)
  • 盒子
  • 写一个类来包装它(本质上是手动装箱)
  • 使用一个数组,并访问它只有直接在数组的索引(不复制到一个变量);这是直接与数组中的项目对话(没有复制)

作为最后一个例子;

if(arr[idx].X == 20) SomeMethod(ref arr[idx]);

. x和在someemethod中的任何用法都是直接访问数组中的值,而不是复制。这只适用于向量(数组),不适用于列表。

一个不可能引用结构体的列表的原因是:它允许你在列表中存储堆栈上变量的地址;数组通常比堆栈上的变量寿命长,所以这将是非常不安全的

在c#中不能创建可存储的结构引用,但是可以为值类型创建引用类型包装器。也就是说,对于一个小结构体,与复制内存相关的开销不会很高。你的分析是否表明这是一个问题?


下面是一个用引用类型包装值类型的例子。请注意,只有当对特定值的所有访问都是通过包装引用类型时,这才有效。这违反了标准的绝缘规则(因为公共字段),但这是一个有点特殊的情况。

public sealed class Reference<T>
    where T: struct
{
    public T Value;
    public Reference(T value)
    {
        Value = value;
    }
}

另一件值得注意的事情是,Reference包装器本身可以接受值null,尽管其内容不可为空。如果您愿意,还可以添加隐式或显式转换操作符以使其更透明。

如果结构体是由简单类型组成的,则可以创建指针数组。是的,c#中的指针对于你这样的情况非常有用。不过也有局限性。原始结构必须存储在Array而不是List<>中。看看下面用unsafe构建标志编译的例子。

[StructLayout(LayoutKind.Sequential)]
public struct Vec3
{
    public double X, Y, Z;
    public double Mag { get { return Math.Sqrt(X * X + Y * Y + Z * Z); } }
}
public unsafe class Vec3ArrayProxy
{
    Vec3*[] ptr = null; //internal array of pointers
    public Vec3ArrayProxy(Vec3[] array)
    {
        ptr = new Vec3*[array.Length]; //allocate array
        fixed (Vec3* src = array) //src holds pointer from source array
        {
            for (int i = 0; i < array.Length; i++)
            {
                ptr[i] = &src[i]; //take address of i-th element
            }                
        }
    }
    public Vec3ArrayProxy(Vec3ArrayProxy other)
    {
        //just use all the existing pointers
        ptr = (Vec3*[])other.ptr.Clone();
        //or I could say:
        //ptr = other.ptr;
    }
    // Access values with index
    public Vec3 this[int index]
    {
        get { return *ptr[index]; }
        set { *ptr[index] = value; }
    }
    public int Count { get { return ptr.Length; } }
    // Access the array of pointers
    public Vec3*[] PtrArray { get { return ptr; } }
    // Copy the values of original array into new array
    public Vec3[] ToArrayCopy()
    {
        Vec3[] res = new Vec3[ptr.Length];
        for (int i = 0; i < res.Length; i++)
        {
            res[i] = *ptr[i];
        }
        return res;
    }
}

unsafe class Program
{
    static void Main(string[] args)
    {
        const int N = 10; //size of array
        // Allocate array in memory
        Vec3[] array = new Vec3[N];
        // Assign values into array
        for (int i = 0; i < N; i++)
        {
            array[i] = new Vec3() { X = i, Y = 0, Z = 0 };
        }
        //Build proxy to array (with pointers)
        Vec3Array A = new Vec3Array(array);
        // Reference the same pointers as A
        Vec3Array B = new Vec3Array(A); 
        // Change the original array
        array[4].X = -4;
        // Or change via a copy
        A.PtrArray[5]->Y = -5;  
        // Or assign a new value
        B[0] = B[9];            
        // Show contents of array via proxy A
        Console.WriteLine("{0,-6}|{1,6}|{2,6}|{3,6}|{4,6}", 
            "i", "X", "Y", "Z", "Mag");
        for (int i = 0; i < N; i++)
        {
            Console.WriteLine("{0,6}|{1,6:F2}|{2,6:F2}|{3,6:F2}|{4,6:F3}", 
                i + 1, A[i].X, A[i].Y, A[i].Z, A[i].Mag);
        }
    }
}

很抱歉这么长的代码,但我想展示结构指针的所有特性。List<>不能工作的原因是因为您不能使用指向列表元素的指针。如果您真的真的必须使用List<>,那么使用以下代码从私有字段_items中提取数组:

    static T[] ExtractArray(List<T> list)
    {
        //list.TrimExcess();
        var t = list.GetType();
        var items = t.GetField("_items", 
            BindingFlags.NonPublic | BindingFlags.Instance);
        return items.GetValue(list) as T[];
    }

尽管它可能很粗糙,但它可以工作,一旦您对List<>进行了一次反射,您就可以将结果缓存在静态字段中,并且每次只调用items.GetValue(list)