пядь <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)
{}

Ответ 1

Это похоже на проблему, вызванную ссылочными сборками, требуемую из-за того, что Span<T> имеет инфраструктурные функции.

Это означает, что в контрольной сборке нет полей (отредактируйте: это не совсем так - см. Сноску).

struct считается назначенной (для целей "определенного назначения"), если все поля назначены, и в этом случае компилятор видит, что "всем нулям нулевые поля присвоены: все хорошее - эта переменная назначается". Но компилятор, похоже, не знает о реальных полях, поэтому его вводят в заблуждение, позволяя что-то, что не является технически обоснованным.

Вы определенно не должны полагаться на это поведение красиво! Хотя в большинстве случаев .locals init должен означать, что вы на самом деле не получаете ничего ужасного. Тем не менее, в настоящее время некоторые незавершенное, чтобы позволить людям, чтобы подавить .locals init в некоторых случаях - Страшно подумать, что может произойти в этом случае здесь - особенно с Span<T> работает так же, как ref T - это может быть очень очень опасно, если поле действительно не инициализировано до нуля.

Интересно, что он уже может быть исправлен: см. Этот пример на sharplab. В качестве альтернативы, возможно, sharplab использует конкретную целевую структуру, а не контрольные сборки.


Редактирование: очень странно, если я загружаю ссылочную сборку в ildasm или отражатель, я вижу:

.field private initonly object _dummy

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


Обновление: видимо, разница здесь - тонкая, но известная проблема с компилятором, которая остается по соображениям совместимости; определенное назначение структур рассматривает частные поля типов, которые известны локально, но не рассматривает частные типы ссылочного типа типов во внешних сборках.

Ответ 2

Марк имеет отличный ответ. Я хотел немного рассказать об истории/контексте.

Прежде всего это определенно ошибка компилятора. По правилам определенного назначения этот локаль определенно не назначается, и любое использование должно быть ошибкой. К сожалению, эта ошибка трудно исправить по ряду причин:

  • Эта ошибка устарела и восходит по крайней мере до С# 4.0. Это дает клиентам 7+ годы непреднамеренно зависеть от этого
  • В BCL есть несколько структур, которые имеют эту основную структуру. Например, CancellationToken.

Те, что взяты вместе, означают, что это может привести к поломке большого количества существующего кода. Несмотря на это, команда С# попыталась исправить ошибку в С# 6.0, когда ошибка была намного моложе. Но попытка скомпилировать источник Visual Studio с этим исправлением показала, что опасения вокруг клиентов, зависящих от этой ошибки, были обоснованными: произошел ряд сбоев в работе. Достаточно, чтобы убедить нас, это окажет негативное влияние на значительный объем кода. Следовательно, исправление было отменено.

Вторая проблема здесь в том, что эта ошибка не была известна всем членам команды компилятора (по крайней мере до сегодняшнего дня). Было ~ 3 года с тех пор, как исправление было отменено и с тех пор немного перевернулось. Члены команды, которые проверяли, как мы создавали ссылочные сборки для Span<T> не знали об этой ошибке и рекомендовали текущий проект на основе спецификации языка. Я один из тех разработчиков :(

Все еще обсуждая это, но, скорее всего, мы собираемся обновить стратегию ссылочной сборки для Span<T> и других типов, чтобы она избегала этой ошибки компилятора.

Спасибо, что сообщили об этом. Извините за путаницу, вызванную :(

Ответ 3

Более или менее это по дизайну, так как оно сильно зависит, если основная 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;
    }
}

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