首页 > 解决方案 > 获取列表数组并保留参考?

问题描述

出于某种性能原因,我想直接在 C# 中获取 List 数组

https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,2765070d40f47b98

我的代码在这里:

private UIVertex[] GetArray(List<UIVertex> verts)
    {
        var aryF = typeof(List<UIVertex>).GetField("_items", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        return aryF.GetValue(verts) as UIVertex[];
    }

private void SetSize(List<UIVertex> list, int size)
{
        sizeF = typeof(List<UIVertex>).GetField("_size", 
        System.Reflection.BindingFlags.NonPublic | 
        System.Reflection.BindingFlags.Instance);
}

static List<UIVertex> verts
static UIVertex[] ary;
//...
ary = GetArray(verts);

就我而言,我可以重用 'verts' ,但是当 'verts' 计数发生变化时。我的ary需要再次获得(这称为反射),

反射成本太高,我可以保留ary reference吗?怎么做?

PS:

当我使用List.Addor的时候List.Get,是触发String.memcpy(),太慢了,这就是为什么我要直接使用数组。而且因为我重用 List ,所以数组长度不会改变。我可以保持它的参考速度很快。

PS 2。

这是之前的修改:

static List<T> Orgin = new List<T>(128);
void DO() // *it call a lot of times.*
{
    GetListByAPI(Orgin);// here is API , So it must insert List<T>.
    int Size = Orgin.Count() * 5; // size is unchanged in here.
    if(Orgin.Capacity < Size) 
    {
        Orgin.Capacity = Size;
    }
    for(int i = Orgin.Count() ; i < Size ;i++)
    {
        T t
        Orgin.Add(t);// I need "Oring.Count() * 5 " Size Array for my logic.
        //...
    }
}

这里是修改后:

static List<T> Orgin = new List<T>(128);
static T[] StaticAry;
void DO() // *it call a lot of times.*
{
    bool LastOringCapacity = Orgin.Capacity;
    GetListByAPI(Orgin);// here is API , So it must insert List<T>.

    bool arrayRefChanged = Orgin.Capacity > LastOringCapacity ; // I Can know it. and get array again. 

    int Size = Orgin.Count() * 5; // size is unchanged in here.
    SetSize(Oring, Size);//here set array legth of list by refection
    StaticAry = GetArray(Orgin);//here I get array of list by reflection
    
    for(int i = 0 ; i < size; i++)
    {
        T t = StaticAry[i];//just use it , avoid to use List.Add api. 
        //....
    }
}

这个静态 Orgin 是重用的,所以 Oring.Capacity 可能不会改变。所以 Orgin 数组也不会调整大小。我想得到数组地址。直接使用它。但现在。我需要使用反射在每个 DO() 中获取数组。

复杂的地方在于 List<T>.Capacity& Array.Length&List<T>.Count()是三个不同的值。

标签: c#performancereflectionreference

解决方案


List<T>Count发生变化时(Capacity实际上),这是因为列表的内部数组已调整大小。

调整更大数组的大小需要分配一个新数组并复制其内容并忘记旧的内存空间。

理论上缩小不是问题,只需要“切割”阵列。

但是由于内存单元的组织,如果不使用具有完整可用空间的全新连续块,就不可能确保增长。

Array.Resize(T[], Int32) 方法

此方法分配一个指定大小的新数组,将元素从旧数组复制到新数组,然后用新数组替换旧数组。数组必须是一维数组。

因此,您无法避免这种情况:每次调整数组大小时,您都需要再次获取它。

因此,除非您这样做一次并且确保阵列的容量不会改变,否则我不推荐您使用的技术,因为很难确保您始终拥有正确的阵列而不是废弃的参考。

因此,您应该使用已经存在的方法List<T>.ToArray()

但事实上,如果可以的话,因为你想要速度,你应该使用Array而不是List.

如您所见,在List<T>.CapacityList发生变化时CapacityCount过度Capacity变化时会发生什么:

T[] newItems = new T[value];
if (_size > 0) {
    Array.Copy(_items, 0, newItems, 0, _size);
}
_items = newItems;

这是Add方法:

public void Add(T item) {
  if (_size == _items.Length) EnsureCapacity(_size + 1);
  _items[_size++] = item;
  _version++;
}

Array.Resize()做同样的事情:

if (larray.Length != newSize) {
  T[] newArray = new T[newSize];
    Array.Copy(larray, 0, newArray, 0,  larray.Length > newSize? newSize : larray.Length);
    array = newArray;
}

我认为这可以针对缩小规模的情况进行优化,但可能有一些与 CLR 或 GC 相关的原因。


推荐阅读