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;
    }
}

Кажется, что в этом случае структура по умолчанию, и именно поэтому она не жалуется на отсутствующее назначение. То, что он не обнаруживает никаких полей, является ошибкой, как объяснено в ответе Марка.

Другие вопросы по тегам