我有一大堆需要提供给OpenGL的值类型。如果这种情况尽快发生会很棒。 我现在正在做的事情是这样的:
List<Vertex> VList = new List<Vertex>();
... //Add vertices
Vertex[] VArray;
VList.CopyTo(VArray, VList.Length);
GL.SetData(..., VArray);
此列表容易10MB大,因此复制速度很慢。我可以不复制地执行此操作,就像以某种方式获取指向List内部使用的数组的指针一样吗?
或者我必须实现自己的List类..
编辑:我忘了提及我不知道将添加到列表中的元素数量。答案 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
答案 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;
}