Ответ 1
Интерфейс IList <T> не так сложно сделать (ну, пока Reflector не свободен и не работает, подсказка подсказки).
Вы можете создать свою собственную реализацию и выставить внутренний массив как общедоступное свойство.
У меня есть большой список типов значений, которые нужно предоставить OpenGL. Было бы здорово, если бы это произошло как можно быстрее. Теперь я делаю следующее:
List<Vertex> VList = new List<Vertex>();
... //Add vertices
Vertex[] VArray;
VList.CopyTo(VArray, VList.Length);
GL.SetData(..., VArray);
Этот список легко 10 МБ большой, поэтому копирование происходит медленно. Могу ли я сделать это без копирования, как-то получить указатель на массив, используемый внутри List?
Или мне нужно реализовать свой собственный класс List.
EDIT: я забыл упомянуть, что я не знаю количество элементов, которые будут добавлены в список.
Интерфейс IList <T> не так сложно сделать (ну, пока Reflector не свободен и не работает, подсказка подсказки).
Вы можете создать свою собственную реализацию и выставить внутренний массив как общедоступное свойство.
Я бы не рекомендовал то, что вы хотите сделать. Почему вы используете 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
).
Если вам нужно многократно обращаться к внутреннему массиву, рекомендуется сохранить его как делегата.
В этом примере он делегирует динамический метод. Первый вызов может быть не быстрым, но последующие вызовы (в списке того же типа) будут намного быстрее.
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);
}
}
Вместо того, чтобы использовать рефлексию для доступа к внутреннему массиву в List<T>
, если вам нужна только возможность добавления, тогда я бы рекомендовал реализовать свой собственный изменяемый размер массива (gasp!). Это не так сложно.
Что-то вроде:
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
.
Вы можете сделать это с отражением:
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);
}
edit: а кто-то уже сказал это, пока я тестировал это.
Возможно, вам стоит подумать, не подходит ли ваш подход к этому. Если вы обнаружите, что используете рефлексию, вы уже проиграли.
Я могу придумать несколько способов приблизиться к этому, хотя тот, который идеален, во многом зависит от того, является ли это многопоточной частью кода или нет.
Предположим, что это не...
Подумайте о характеристиках массива. Каждый раз, когда этот метод называется массивом длины N, создается. Ваша цель - повысить производительность (это означает, что вы хотите минимизировать распределение и копии данных).
Можете ли вы намекнуть на компиляцию или время выполнения, какой идеальный начальный размер для массива? Я имею в виду - если 95% времени N-длина составляет 100 тыс. Или меньше... начните с массива элементов 100 тыс. Единиц. Продолжайте использовать его, пока не нажмете случай, когда массив слишком мал.
Когда вы попадаете в этот случай, вы можете решить, что вы делаете, основываясь на своем понимании программы. Должен ли массив расти на 10%? Должно ли оно вырасти до буквальной необходимой длины? Можете ли вы использовать то, что у вас есть, и продолжить процесс для остальных данных?
Со временем будет найден идеальный размер. Вы можете даже настроить свою программу на конечный размер каждый раз, когда она запускается, и использовать ее как подсказку для выделения при следующем запуске (возможно, длина этого массива зависит от факторов окружающей среды, таких как разрешение и т.д.).
Другими словами, я предлагаю, чтобы вы не использовали метод List-to-Array, и что вы предварительно выделяете массив, держите его навсегда и вырабатываете его по мере необходимости.
Если ваша программа имеет проблемы с потоками, вам, очевидно, необходимо будет их решить.
Вы могли бы получить указатель из общего списка, но я бы не рекомендовал его, и он, вероятно, не будет работать так, как вы ожидали бы (если вообще). В основном это означает получение указателя на объект, а не структуру памяти, такую как массив.
Я думаю, вы должны пойти об этом по-другому, и если вам нужна скорость, тогда работайте непосредственно в массиве байтов, используя указатель на структурный массив в небезопасном контексте.
Фоновая информация:
" Даже при использовании с ключевым словом unsafe, адрес адреса управляемого объекта, получение размера управляемого объекта или объявление указателя на управляемый тип не допускается ". - Из С#: преобразовать общий указатель в массив
Поскольку вы используете GL, я предполагаю, что вы знаете, что делаете, и пропустите все оговорки. Попробуйте это или посмотрите fooobar.com/info/233561/...
[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;
}