Нет необходимости фиксировать строку или указатели на этом этапе, и на самом деле это было бы концептуально некорректно.После нулевой проверки / возврата (которая также должна проверять длину 0) вы можете сделать это:
var strSpan = _String.AsSpan();
return ref strSpan[_Index];
Это, как указывает имя функции, возвращает закрепляемую ссылку на первый символ базовой строки,То, что вы получили выше, возвращает ссылку на первый разыменованный элемент закрепленного указателя на нижележащую строку, но время жизни закрепления ограничено областью действия фиксированного оператора (это может быть неправильно, но я не думаю, чтовозвращенный реф держит его приколол).Использование ссылки на элемент Span позволяет избежать ненужного закрепления.
Обновление - я немного больше посмотрел на закрепленные указатели (куча бесполезных поисков в Google - вернулся к декомпиляции / экспериментам).Оказывается, возвращение ссылки на элемент закрепленного указателя не сохраняет этот вывод.Возвращенная ссылка является ссылкой на разрешенный адрес - весь контекст закрепления потерян.
Я подумал, что, возможно, изменение _String
на Memory<char>
(используйте расширение AsMemory
в конструкторе) может работать.Тогда ваш GetPinnableReference
может просто return ref _String[0];
, но AsMemory
дает вам объект памяти только для чтения (не может вернуть доступную для записи ссылку).
Если не реализовать IPinnable
с GCHandle
и IDisposable
или MemoryManager<T>
(тяжелее, чем вы хотите наверняка), я думаю, что мое решение Span<char>
, описанное выше, может быть наиболее безопасным / правильным способом для достижения этой цели.
Вот разборка GetPinnableReference (не противмое пространство имен PowerPC.Test - я делаю бинарный дизассемблер / эмулятор / отладчик PowerPC .NET Standard 1.1 и использую эти концепции с большим интересом, поэтому я заинтересован):
.method public hidebysig instance char& GetPinnableReference() cil managed
{
// Code size 34 (0x22)
.maxstack 2
.locals init (char* V_0,
string pinned V_1,
char& V_2)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld string PowerPC.Test.Console.Program/PinnableStruct::_string
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: conv.u
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: brfalse.s IL_0016
IL_000e: ldloc.0
IL_000f: call int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
IL_0014: add
IL_0015: stloc.0
IL_0016: nop
IL_0017: ldloc.0
IL_0018: call !!0& [System.Runtime.CompilerServices.Unsafe]System.Runtime.CompilerServices.Unsafe::AsRef<char>(void*)
IL_001d: stloc.2
IL_001e: br.s IL_0020
IL_0020: ldloc.2
IL_0021: ret
} // end of method SomeInfo::GetPinnableReference
Я не ожидаю, что большинствочитатели, чтобы иметь возможность читать IL, но это нормально.Ответ, который мы ищем, находится прямо вверху (и подтверждается в IL после него).У нас есть три местных жителя: char* V_0
, string pinned V_1
и char& V_2
.Для меня это проливает немного света на то, почему фиксированный синтаксис такой, какой он есть.У вас есть фиксированное ключевое слово, представленное string pinned V_1
, указатель на символ (из вашего кода), представленный char* V_0
, и возвращаемая ссылка, представленная char& V_2
.Три отдельные концепции представлены тремя отдельными конструкциями кода.
Итак, ваш фиксированный оператор закрепляет строку и присваивает ссылку закрепления V_1
при IL_0002
IL_0007
, затем загружает V_1
, преобразует его в указатель (преобразование в целое число без знака) исохраняет его в V_0
(ваш char* p
) в IL_0008
до IL_000a
, наконец, он загружает V_0
, вычисляет смещение char
, указанное в _index
, и сохраняет его обратно в V_0
от IL_000e
до IL_0015
.В рамках фиксированного оператора он загружает V_0, преобразует его в ссылку с помощью Unsave.AsRef<char>
и сохраняет ее в V_2
(ссылка, которая будет возвращена) с IL_0017
до IL_001d
.Я не уверен, почему компилятор C # делает это, но последний бит от IL_001e
до IL_0021
часто - то, как компилятор обрабатывает возвращение значения;он вставляет ветвь в блок, который будет возвращать значение (даже если в этом случае это следующая инструкция), затем он загружает возвращаемое значение из локальной переменной (которую он создал и IMO необязательно назначается, но, возможно, естьлогическое объяснение этому?) и, наконец, возвращает результат, который вы получили несколько инструкций назад.
Вот вам ответ, наконец-то!Невозможно сохранить закрепление в вашем фиксированном выражении, потому что оно разыменовывает неуправляемый указатель V_0
/ p
, который в данном случае просто поддерживается ссылкой закрепления, V_1
, что является инструкцией для разыменованияуказатель не знаю о).Команда CLR могла бы быть умнее в JIT и обработать этот случай специально с помощью анализа, связав ссылку на символ с закрепленной ссылкой, но я думаю, что это было бы странно с архитектурной точки зрения, трудно объяснить / документировать, и я сомневаюсь, что они пошли такмаршрут, поэтому я не пошел, чтобы проверить код CLR.