Могу ли я получить указатель на Span?

У меня есть (ReadOnly)Span<byte>, из которого я хочу декодировать строку.

Только в .NET Core 2.1 у меня есть новая перегрузка для декодирования строки из нее без необходимости копировать байты:

Encoding.GetString(ReadOnlySpan<byte> bytes);

В .NET Standard 2.0 и .NET 4.6 (которые я также хочу поддерживать) у меня есть только классические перегрузки:

Encoding.GetString(byte[] bytes);
Encoding.GetString(byte* bytes, int byteCount);

Для первого требуется копия байтов в массив, которого я хочу избежать.
Для второго требуется указатель байта, поэтому я подумал о том, чтобы получить его из своего диапазона, например

Encoding.GetString(Unsafe.GetPointer<byte>(span.Slice(100)))

... но мне не удалось найти реальный метод для этого. Я попробовал void* Unsafe.AsPointer<T>(ref T value), но я не могу передать ему диапазон и не нашел другого метода, работающего с указателями (и диапазонами).

Возможно ли это вообще, и если да, то как?


person Ray    schedule 18.01.2019    source источник
comment
Введите [ReadOnly]Span<T>.GetPinnableReference(). При использовании C# 7.3 использовать это так же просто, как fixed (byte* bytes = span) - это компилируется для .NET 4.5.2, по крайней мере, я не проверял, будет ли он действительно работать (у меня есть только более поздние фреймворки установлены).   -  person Jeroen Mostert    schedule 18.01.2019
comment
В предыдущих версиях C# можно было использовать возвращаемое значение ref GetPinnableReference() в качестве аргумента для Unsafe.AsPointer. Вам нужно как минимум C# 7.0, чтобы использовать ref locals.   -  person Jeroen Mostert    schedule 18.01.2019
comment
@JeroenMostert Это здорово, я использую C # 7.3, и он работает гладко, как шелк. Вы хотите опубликовать ответ об этом, чтобы я мог его принять?   -  person Ray    schedule 18.01.2019
comment
С помощью ILSpy я нашел более удобный синтаксис и для более ранних версий. Также не тестировался, но, поскольку возвращаемые указатели идентичны, я предполагаю, что это работает.   -  person Jeroen Mostert    schedule 18.01.2019


Ответы (2)


Если у вас есть C# 7.3 или более поздняя версия, вы можете использовать расширение, сделанное для оператора fixed, которое может использовать любой подходящий метод GetPinnableReference для типа (который есть у Span и ReadOnlySpan):

fixed (byte* bp = bytes) {
    ...
}

Поскольку мы имеем дело с указателями, для этого, конечно же, требуется контекст unsafe.

В C# 7.0–7.2 этого нет, но разрешено следующее:

fixed (byte* bp = &bytes.GetPinnableReference()) {
    ...
}
person Jeroen Mostert    schedule 18.01.2019
comment
Довольно круто. Я принимаю это как ответ, поскольку он также охватывает новый синтаксис С# 7.3. - person Ray; 18.01.2019

Попробуй это:

Span<byte> bytes = ...;
string s = Encoding.UTF8.GetString((byte*)Unsafe.AsPointer(ref bytes.GetPinnableReference()),
    bytes.Length);
person mm8    schedule 18.01.2019
comment
Спасибо! Я не видел GetPinnableReference() нигде в IntelliSense. Однако это не сработало для ReadOnlySpan<byte> (он не может передать его как ref, потому что он доступен только для чтения); но это сработает для моего первоначального вопроса (только сейчас я понял, что у меня есть ReadOnlySpan, извините!). - person Ray; 18.01.2019
comment
@RayKoopa: кстати, причина, по которой вы не могли увидеть этот метод, заключается в том, что он был явно скрыт как функция. Это было еще тогда, когда его называли DangerousGetPinnableReference; с тех пор он был понижен до не такого опасного, но метод все еще скрыт. - person Jeroen Mostert; 18.01.2019
comment
Альтернатива, предложенная для ReadOnlySpan, неверна! Сначала сохранив его в переменной и взяв ref, вы получите ссылку на локальный byte, не составной массив. Доступ к любому элементу за пределами первого таким образом даст мусор. - person Jeroen Mostert; 18.01.2019
comment
@JeroenMostert Я действительно помню это название опасного метода. Я не следил за последними изменениями, спасибо, что прояснили это. - person Ray; 18.01.2019