Span<T> не требует назначения локальной переменной. Это особенность?
Я заметил, что следующее будет компилироваться и выполняться, даже если локальные переменные не инициализированы. Это особенность Span?
void Uninitialized()
{
Span<char> s1;
var l1 = s1.Length;
Span<char> s2;
UninitializedOut(out s2);
var l2 = s2.Length;
}
void UninitializedOut(out Span<char> s)
{}
3 ответа
Это выглядит как проблема, вызванная ссылочными сборками, необходимыми из-за способа, которым Span<T>
имеет специфичные для каркаса внутренние устройства.
Это означает, что в справочной сборкенет полей (правка: это не совсем так - см. Сноску).
struct
считается назначенным (для целей "определенного назначения"), если все поля назначены, и в этом случае компилятор видит "все нули из нулевых полей были назначены: все хорошо - эта переменная назначена". Но компилятор, похоже, не знает о реальных полях, поэтому его вводят в заблуждение, допуская что-то, что не является технически допустимым.
Вы определенно не должны полагаться на это вежливое поведение! Хотя в большинстве случаев .locals init
должно означать, что на самом деле вы не получите ничего слишком ужасного. Тем не менее, в настоящее время проводится определенная работа, чтобы позволить людям подавлять .locals init
в некоторых случаях - я боюсь думать, что может произойти в этом сценарии здесь - особенно с Span<T>
работает так же, как ref T
- это может стать очень и очень опасным, если поле действительно не инициализируется нулем.
Интересно, что это может быть уже исправлено: посмотрите этот пример на sharplab. В качестве альтернативы, возможно, sharplab использует конкретные целевые рамки, а не эталонные сборки.
Редактировать: очень странно, если я загружаю справочную сборку в ildasm
или отражатель, я вижу:
.field private initonly object _dummy
это подделанное поле в эталонной сборке, которое предназначено для предотвращения этого, но... похоже, сейчас оно работает не очень надежно!
Обновление: очевидно, здесь есть небольшая, но известная проблема с компилятором, которая остается из соображений совместимости; При определенном присваивании структур рассматриваются частные поля типов, которые известны локально, но не учитываются частные поля типов ссылочных типов во внешних сборках.
У Марка отличный ответ. Я хотел бы немного рассказать об истории / контексте.
Прежде всего, это определенно ошибка компилятора. По правилам определенного присваивания этот локальный объект точно не назначен, и любое использование должно быть ошибкой. К сожалению, эту ошибку трудно исправить по ряду причин:
- Эта ошибка старая и восходит как минимум к C# 4.0. Это дает клиентам 7+ лет, чтобы непреднамеренно взять на себя зависимость
- Есть ряд структур в BCL, которые имеют эту базовую структуру. Например CancellationToken.
Взятые вместе означают, что исправление может привести к поломке большого количества существующего кода. Несмотря на это, команда C# пыталась исправить ошибку в C# 6.0, когда она была намного моложе. Но попытка скомпилировать исходный код Visual Studio с помощью этого исправления показала, что опасения, связанные с тем, что клиенты берут зависимость от этой ошибки, были вполне обоснованными: было несколько перерывов в сборке. Достаточно, чтобы убедить нас, что это окажет негативное влияние на значительный объем кода. Следовательно, исправление было отменено.
Вторая проблема здесь заключается в том, что эта ошибка не была известна всем членам команды компилятора (по крайней мере, до сегодняшнего дня). Прошло ~3 года с тех пор, как исправление было отменено и с тех пор было немного изменено. Члены команды, которые проверяли, как мы создавали эталонные сборки для Span<T>
мы не знали об этой ошибке и рекомендовали текущий дизайн на основе спецификации языка. Я один из тех разработчиков:(
Все еще обсуждаем это, но, скорее всего, мы собираемся обновить стратегию эталонной сборки для Span<T>
и другие типы, чтобы избежать этой ошибки компилятора.
Спасибо за сообщение об этом. Извините за путаницу, вызванную:(
В большей или меньшей степени это сделано по замыслу, так как сильно зависит struct
держит любые поля сам.
Этот код компилируется, например:
public struct MySpan<T>
{
public int Length => 1;
}
static class Program
{
static void Main(string[] args)
{
MySpan<char> s1;
var l1 = s1.Length;
}
}
Но этот код не:
public struct MySpan<T>
{
public int Length { get; }
}
static class Program
{
static void Main(string[] args)
{
MySpan<char> s1;
var l1 = s1.Length;
}
}
Кажется, что в этом случае структура по умолчанию, и именно поэтому она не жалуется на отсутствующее назначение. То, что он не обнаруживает никаких полей, является ошибкой, как объяснено в ответе Марка.