Учитывая управляемую ссылку на структуру, как получить управляемую ссылку на поле со смещением? - PullRequest
2 голосов
/ 30 мая 2019

Я пытаюсь реализовать массивы фиксированного размера в C #, которые параметризуются хакерскими числами уровня типа.См. GitHub

Я хочу реализовать метод, который бы работал следующим образом:

static ref T Ref(this ref TContainer container, int index)
  where T: unmanaged
  where TContainer: unmanaged
  => ref container[index];

Теперь проблема заключается в следующем: я знаю, что TContainer внутренне состоитиз N полей типа T.Но на самом деле он не реализует операцию индексации (или какой-либо другой интерфейс).

Я могу сделать следующее:

CheckIndex(index);
fixed (TContainer* self = &container) {
    T* data = (T*)self;
    return ref data[index];
}

Это компилируется с unsafe, но у меня есть два вопроса:

  1. Будет ли правильно отслеживаться время жизни управляемой ссылки, полученной таким образом?
  2. Влияет ли использование fixed в этом контексте на производительность?(например, закрепление GC, проблемы с алиасами и т. д.)

Кроме того, пример с fixed и return ref сверху также компилируется, даже когда я заменяю тип параметра ref TContainer просто TContainerили, что должно быть одинаковым, просто объявите его как член экземпляра вместо метода расширения.Это даже работает в тестовом примере, но я не могу понять, почему это так, потому что в нормальных условиях нельзя возвращать ref this.field из метода экземпляра struct.

1 Ответ

3 голосов
/ 30 мая 2019

В ответ на ваши вопросы:

  1. да, потому что вы восстанавливаете неуправляемый указатель обратно в управляемый указатель, пока он закреплен;ни при каких условиях управляемый указатель не может быть недействительным
  2. нет, fixed использует механизм очень низких издержек - в основном, fixed просто помечает локальную переменную как имеющую особое значение;когда GC запускается, он уже обходит кадры стека, и когда он это делает, он ищет эти локальные объекты и отмечает, что их значение следует считать закрепленным

Тем не мение!Похоже, что концепция, которой вы действительно придерживаетесь, это Span<T> (или Memory<T>) - Span<T> - это в основном «диапазон», использующий управляемые указатели.

Вот версия, которая не используетunsafe:

public static ref T Ref<T, TContainer>(this ref TContainer container, int index)
    where TContainer : unmanaged
    where T : unmanaged
{
    if (index < 0 || Unsafe.SizeOf<T>() * (index + 1) > Unsafe.SizeOf<TContainer>())
        throw new ArgumentOutOfRangeException(nameof(index));
    ref T first = ref Unsafe.As<TContainer, T>(ref container);
    return ref Unsafe.Add<T>(ref first, index);
}

Обратите внимание, что он все еще "небезопасен".

...