列出<t>到T []而不复制</t>

时间:2011-02-11 19:02:55

标签: c# arrays list

我有一大堆需要提供给OpenGL的值类型。如果这种情况尽快发生会很棒。 我现在正在做的事情是这样的:

List<Vertex> VList = new List<Vertex>();
... //Add vertices
Vertex[] VArray;
VList.CopyTo(VArray, VList.Length);
GL.SetData(..., VArray);

此列表容易10MB大,因此复制速度很慢。我可以不复制地执行此操作,就像以某种方式获取指向List内部使用的数组的指针一样吗?

或者我必须实现自己的List类..

编辑:我忘了提及我不知道将添加到列表中的元素数量。

8 个答案:

答案 0 :(得分:25)

如果您需要重复访问内部数组,最好将访问者存储为委托。

在此示例中,它是动态方法的委托。第一次调用可能不会很快,但后续调用(在相同类型的列表上)会快得多。

public static class ListExtensions
{
    static class ArrayAccessor<T>
    {
        public static Func<List<T>, T[]> Getter;

        static ArrayAccessor()
        {
            var dm = new DynamicMethod("get", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(T[]), new Type[] { typeof(List<T>) }, typeof(ArrayAccessor<T>), true);
            var il = dm.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0); // Load List<T> argument
            il.Emit(OpCodes.Ldfld, typeof(List<T>).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance)); // Replace argument by field
            il.Emit(OpCodes.Ret); // Return field
            Getter = (Func<List<T>, T[]>)dm.CreateDelegate(typeof(Func<List<T>, T[]>));
        }
    }

    public static T[] GetInternalArray<T>(this List<T> list)
    {
        return ArrayAccessor<T>.Getter(list);
    }
}

确保包括:

using System.Reflection;
using System.Reflection.Emit;

答案 1 :(得分:13)

我不建议你想做什么。你为什么一开始使用List<T>?如果您可以准确地告诉我们您要创建的数据结构应该具有哪些特征,以及它应该如何与消费API进行交互,我们可能能够为您的问题提供适当的解决方案。

但我会按照要求回答这个问题。

  

我可以不复制地这样做,比如   以某种方式得到一个指向数组的指针   由List内部使用?

是的,尽管您将依赖未记录的实现细节。从.NET 4.0开始,支持数组字段称为_items

Vertex[] vertices = (Vertex[]) typeof(List<Vertex>)
                   .GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance)
                   .GetValue(VList);

请注意,此数组几乎肯定会在末尾有松弛(这是List<T>的整点),因此此数组上的array.Length将不会全部那很有用。消耗数组的API需要通过其他方式(通过告诉它列表的真实Count是什么)通知数组的“实际”长度。

答案 2 :(得分:8)

IList<T>界面并不难做到(好吧,只要Reflector是免费的并且正常运行,提示提示)。

您可以创建自己的实现并将内部数组公开为公共属性。

答案 3 :(得分:5)

如果您只需要能够添加,而不是使用反射来访问List<T>中的内部数组,那么我实际上建议您实现自己的可调整大小的数组(喘息!) 。 这并不难。

类似的东西:

class ResizableArray<T>
{
    T[] m_array;
    int m_count;

    public ResizableArray(int? initialCapacity = null)
    {
        m_array = new T[initialCapacity ?? 4]; // or whatever
    }

    internal T[] InternalArray { get { return m_array; } }

    public int Count { get { return m_count; } }

    public void Add(T element)
    {
        if (m_count == m_array.Length)
        {
            Array.Resize(ref m_array, m_array.Length * 2);
        }

        m_array[m_count++] = element;
    }
}

然后,您可以使用InternalArray获取内部数组,并使用Count了解数组中有多少项。

答案 4 :(得分:3)

你可以用反射来做到这一点:

public static T[] GetUnderlyingArray<T>(this List<T> list)
{
    var field = list.GetType().GetField("_items",
        System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.NonPublic);
    return (T[])field.GetValue(list);
}

编辑:啊,当我在测试时,有人已经说过了..

答案 5 :(得分:1)

您可能需要考虑您的方法是否错误。如果你发现自己使用反射来做到这一点 - 你已经迷失了。

我可以想出几种方法来解决这个问题,但哪一种方法是理想的,这取决于它是否是一个多线程的代码片段。

我们假设它不是......

考虑一下数组的特性。每次调用此方法时,都会创建一个N长度数组。您的目标是提高性能(这意味着您希望最小化分配和数据副本)。

您能否在编译或运行时提示数组的理想起始大小是什么?我的意思是 - 如果95%的时间N长度是100k或更少......从100k项目阵列开始。继续使用它,直到遇到阵列太小的情况。

当您遇到此案例时,您可以根据您对该计划的理解来决定您的工作。阵列应该增长10%吗?它应该增长到文字所需的长度吗?你可以使用你拥有的东西并继续处理剩下的数据吗?

随着时间的推移,将找到理想的尺寸。您甚至可以让程序在每次运行时监视最终大小,并在下次启动时将其用作分配提示(可能此数组长度取决于环境因素,如分辨率等)。

换句话说 - 我建议你不要使用List-to-Array方法,并预先分配一个数组,永久保存它,并根据需要增长它。

如果您的程序存在线程问题,您显然需要解决这些问题。

答案 6 :(得分:0)

您可能能够从通用列表中获取指针,但我不推荐它,它可能不会按照您期望的方式工作(如果有的话)。基本上它意味着获取指向对象的指针,而不是像数组那样的内存结构。

我认为你应该反过来说,如果你需要速度,那么在不安全的上下文中使用结构数组指针直接在字节数组上工作。

背景资料:
“即使与unsafe关键字一起使用,也不允许获取托管对象的地址,获取托管对象的大小或声明指向托管类型的指针。” - 来自C#: convert generic pointer to array

MSDN unsafe

答案 7 :(得分:0)

由于您正在使用GL,我会假设您知道自己在做什么,并跳过所有警告。试试这个,或参见https://stackoverflow.com/a/35588774/194921

  [StructLayout(LayoutKind.Explicit)]
  public struct ConvertHelper<TFrom, TTo>
      where TFrom : class
      where TTo : class {
    [FieldOffset( 0)] public long before;
    [FieldOffset( 8)] public TFrom input;
    [FieldOffset(16)] public TTo output;

    static public TTo Convert(TFrom thing) {
      var helper = new ConvertHelper<TFrom, TTo> { input = thing };
      unsafe {
        long* dangerous = &helper.before;
        dangerous[2] = dangerous[1];  // ie, output = input
      }
      var ret = helper.output;
      helper.input = null;
      helper.output = null;
      return ret;
    }
  }

  class PublicList<T> {
    public T[] _items;
  }

  public static T[] GetBackingArray<T>(this List<T> list) {
    return ConvertHelper<List<T>, PublicList<T>>.Convert(list)._items;
  }