简体   繁体   English

将结构引用添加到C#列表中

[英]Adding reference to structures into C# lists

Trying to keep my C# code optimized, I found that if I have a list of structure elements, each insertion would be a full copy - something that I would like to avoid. 试图保持我的C#代码优化,我发现如果我有一个结构元素列表,每个插入将是一个完整的副本 - 我想避免的东西。

In C++ I would simply keep a list of pointers and so I was wondering if I can do the same using C#, maybe by having the list as a list of references to the structure. 在C ++中,我只是保留一个指针列表,所以我想知道我是否可以使用C#做同样的事情,也许将列表作为结构的引用列表。 Unfortunately the structure cannot be turned into a class as it is part of the XNA library (Vector3, Matrix, etc...) In any case - how would the format and usage look like if it's possible? 不幸的是,结构不能变成一个类,因为它是XNA库的一部分(Vector3,Matrix等)。无论如何 - 如果可能,格式和用法会如何?

Thanks. 谢谢。

You can't create storeable references to structures in C#, but you could create a reference type wrapper for your value type. 您无法在C#中创建对结构的可存储引用,但您可以为您的值类型创建引用类型包装器。 That said, the overhead associated with copying the memory is not going to be high for a small structure. 也就是说,与复制内存相关的开销对于小型结构来说不会很高。 Has your profiling shown that this is a problem? 您的分析是否表明这是一个问题?


Below is an example of a value type wrapped in a reference type. 下面是包含在引用类型中的值类型的示例。 Note that this only works if all access to a particular value is through the wrapping reference type. 请注意,这仅适用于对特定值的所有访问都是通过包装引用类型的情况。 This violates standard rules of insulation (because of the public field), but this is a bit of a special case. 这违反了标准的绝缘规则(因为公共领域),但这是一个特例。

public sealed class Reference<T>
    where T: struct
{
    public T Value;

    public Reference(T value)
    {
        Value = value;
    }
}

Another thing worth noting is that the Reference wrapper itself can take on the value null, though its contents are non-nullable. 另一件值得注意的事情是,Reference包装器本身可以采用null值,尽管它的内容是不可为空的。 You can also add implicit or explicit conversion operators to make this more transparent, if you wish. 如果您愿意,还可以添加隐式或显式转换运算符以使其更透明。

No, basically. 不,基本上。 Options: 选项:

  • use a class (you've already said you can't) 使用一个类(你已经说过你不能)
  • box it 盒子吧
  • write a class that wraps it (essentially, manual boxing) 写一个包装它的类(基本上是手动装箱)
  • use an array, and access it only directly in the array by index (without copying into a variable); 使用数组,并且只能通过索引直接在数组中访问它(不需要复制到变量中); this is then talking directly to the item in the array (no copy) 然后直接与数组中的项目对话(无副本)

As an example of the last; 作为最后一个例子;

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

Both the .X, and any usage within SomeMethod, are accessing the value directly in the array, not a copy. .X和SomeMethod中的任何用法都是直接在数组中访问值,而不是副本。 This is only possible with vectors (arrays), not lists. 这仅适用于矢量(数组),而不是列表。

One reason a list of refs to structs isn't possible: it would allow you I store, in the list, the address of a variable on the stack; 建立refs列表的一个原因是不可能的:它允许你在列表中存储堆栈中变量的地址; the array usually outlives the variable on the stack, so that would be ludicrously unsafe 数组通常比堆栈上的变量更长,所以这将是非常不安全的

If the structs are made of simple types then you can make an array of pointers. 如果结构由简单类型组成,那么您可以创建一个指针数组。 Yes pointers in C# are very useful for cases like yours. C#中的指针对于像你这样的情况非常有用。 There are limitations though. 但是有一些限制。 The original structures have to be stored in an Array and not a List<> . 原始结构必须存储在Array而不是List<> Look at the example below compiled with unsafe build flag. 查看使用unsafe build flag编译的下面示例。

[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);
        }

    }
}

Sorry for the long code, but I wanted to show all the features of struct pointers. 很抱歉长代码,但我想展示struct指针的所有功能。 The reason that List<> will not work is because you cannot take a pointer to a list element. List<>不起作用的原因是因为您无法获取指向列表元素的指针。 If you really really really must use a List<> , then extract the array from the private field _items with the following code: 如果你真的真的必须使用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[];
    }

Crude as it may be, it works and once you have made the reflection on List<> once, you can cache the results in a static field and only call the items.GetValue(list) each time. 粗体,它可以工作,一旦你在List<>上做了一次反射,就可以将结果缓存在一个静态字段中,每次只调用items.GetValue(list)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM