List<T> 到 T[] 不复制
Posted
技术标签:
【中文标题】List<T> 到 T[] 不复制【英文标题】:List<T> to T[] without copying 【发布时间】:2011-06-25 18:27:38 【问题描述】:我有大量需要提供给 OpenGL 的值类型。如果这能尽快发生,那就太好了。 我现在正在做的事情是这样的:
List<Vertex> VList = new List<Vertex>();
... //Add vertices
Vertex[] VArray;
VList.CopyTo(VArray, VList.Length);
GL.SetData(..., VArray);
这个列表很容易有 10MB 大,所以复制很慢。我可以在不复制的情况下做到这一点,比如以某种方式获取指向 List 内部使用的数组的指针吗?
还是我必须实现自己的 List 类..
编辑:我忘了提到我不知道将添加到列表中的元素数量。
【问题讨论】:
@Iain,大概他不知道他将使用多少个Vertex
对象。
因为我不知道开头的长度。我必须制作一个大得离谱的数组,然后调整它的大小。
@Hannesh,我的猜测是,因为我还没有调查过,所以列表的后备数组将有许多空槽,这些空槽将在随后的Add
操作中被填充。这是为了便于快速添加操作,而不必不断调整数组的大小(当然,它会在必要时这样做)。因此,1) 对后备数组的直接引用可能仍需要一些清理。 2) 如果您真的关心性能,请注意此调整大小并做出相应计划。
@Anthony:List如果您需要重复访问内部数组,最好将访问器存储为委托。
在这个例子中,它是动态方法的委托。第一次调用可能不会很快,但后续调用(在相同类型的 List 上)会快得多。
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;
【讨论】:
【参考方案2】:我不会推荐你想做的事。你为什么首先使用List<T>
?如果您能准确地告诉我们您想要创建的数据结构应该具有哪些特征,以及它应该如何与消费 API 交互,我们或许能够为您的问题提供适当的解决方案。
但我会尽力回答问题。
我可以在不复制的情况下做到这一点,例如 以某种方式获得指向数组的指针 List 内部使用?
是的,尽管您将依赖未记录的实现细节。从 NET 4.0 开始,支持数组字段称为 _items
。
Vertex[] vertices = (Vertex[]) typeof(List<Vertex>)
.GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(VList);
请注意,这个数组的末尾几乎肯定会有 slack(这就是List<T>
的全部意义),所以这个数组上的array.Length
不会那么有用。需要通过其他方式通知使用数组的 API 数组的“真实”长度(通过告诉它列表的真实 Count
是什么)。
【讨论】:
+1,但实际上,OP 应该只创建自己的数据结构。 请放心,MS 不会在下一个版本中更改内部实现... 你会推荐什么而不是列表?我对想法持开放态度。 @Hannesh 推出你自己的IList<T>
实现。
@Hannesh,其实我只是建议你重新实现List<T>
,公开暴露内部数组。【参考方案3】:
IList<T> 接口并不难做到(好吧,只要 Reflector 是免费的并且可以正常工作,提示提示)。
您可以创建自己的实现并将内部数组公开为公共属性。
【讨论】:
我什至不会打扰IList<T>
,只需包装一个T[]
并引入一个Add
方法。 IndexOf
、Insert
等——不需要。
@Dan 依赖于他的其余代码。如果他正在听我们的主人和指挥官 Jeffrey Richter 的话,他将能够将他的新集合用于任何采用 IEnumerable、IList、IEnumerableIList
部分(那是另一只野兽!)。但是,由于我不知道他可能会将其用于什么else,所以您是对的;我可能完全错了。让我这样说吧:如果实际上我需要的只是一个可以添加并仍然作为数组访问的数组,我绝对不会费心实现IList<T>
。也许IEnumerable<T>
,只是因为它太琐碎了。
@Dan sokay,你的打击是正义的。他必须从他的整体设计中确定实现该接口是否有任何好处。
@DanTao 我有类似的问题,但由于使用了一些闭源 API,我无法使用 IList与其使用反射来访问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
知道数组中有多少项。
【讨论】:
我不想假设消费 API 将如何处理数组末尾的“slack”。 @Iain:我也不会,但如果它可以接受length
参数,那将是完美的。否则我不知道 OP 真正希望的是什么;我怀疑他是否期望 List<T>
在每次调用 Add
时将其内部数组的大小调整为 1 (想象一下,保证 O(N) 追加!)。
@Lain 显然他必须考虑到这一点。 OP 使用它来对抗 OpenGL 堆栈,所以我敢打赌 OP 调用的许多采用数组的方法也采用数组长度。
@Iain:我是这样看的。 API 肯定需要一个 array,否则 OP 的问题将没有意义(List<T>
是 IEnumerable
,所以他可以通过其他方式)。另一方面,我假设它可以选择接受length
参数;否则 OP 的希望将是徒劳的,因为毕竟没有办法避免将元素复制到适当大小的数组中。如果不是这样,我认为 OP 有点搞砸了 ;)
@Dan:对不起,我只是说如果它确实期望一个 IEnumerable 会更好。 @Will:如果是这样,没问题:)【参考方案5】:
您可以通过反射来做到这一点:
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);
编辑:啊,在我测试这个时有人已经说过了..
【讨论】:
【参考方案6】:您可能需要考虑您的处理方法是否错误。如果你发现自己使用反射来做到这一点 - 你已经迷路了。
我可以想出几种方法来解决这个问题,但哪种方法最理想在很大程度上取决于这是否是多线程代码。
让我们假设它不是......
想想数组的特性。每次调用此方法时,都会创建一个 N 长度数组。您的目标是提高性能(这意味着您希望最小化分配和数据副本)。
您能否在编译或运行时提示数组的理想起始大小是多少?我的意思是 - 如果 95% 的时间 N 长度是 100k 或更少......从 100k 项目数组开始。继续使用它,直到遇到数组太小的情况。
当您遇到这种情况时,您可以根据您对程序的理解来决定您要做什么。阵列是否应该增长 10%?它应该增长到字面所需的长度吗?您可以使用现有的数据并继续处理其余数据吗?
随着时间的推移会找到理想的尺寸。您甚至可以让您的程序在每次运行时监控最终大小,并将其用作下次启动时分配的提示(也许这个数组长度取决于环境因素,例如分辨率等)。
换句话说,我的建议是不要使用 List-to-Array 方法,而是预先分配一个数组,将其永久保留,并根据需要进行扩展。
如果您的程序存在线程问题,您显然需要解决这些问题。
【讨论】:
【参考方案7】:您可能能够从通用列表中获取指针,但我不推荐它,它可能不会按照您期望的方式工作(如果有的话)。基本上它意味着获取一个指向对象的指针,而不是像数组这样的内存结构。
我认为你应该反过来做,如果你需要速度,然后在不安全的上下文中使用结构数组指针直接处理字节数组。
背景信息: “即使与 unsafe 关键字一起使用,也不允许获取托管对象的地址、获取托管对象的大小或声明指向托管类型的指针。” - 来自C#: convert generic pointer to array
MSDN unsafe
【讨论】:
哇——他想要的只是数组。例如,OP 可以轻松地使用反射来解决它。不确定所有这些带有不安全代码的业务是关于什么的。【参考方案8】:由于您使用的是 GL,因此我假设您知道自己在做什么,并跳过所有警告。试试这个,或查看https://***.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;
【讨论】:
以上是关于List<T> 到 T[] 不复制的主要内容,如果未能解决你的问题,请参考以下文章
IEnumeration<T> 和 List<T> 之间的区别? [复制]