из параметров структуры типа не требуется назначать

9

Я заметил странное поведение в моем коде, когда случайно закомментировал строку в функции во время проверки кода. Это было очень трудно воспроизвести, но я приведу аналогичный пример здесь.

У меня есть этот тестовый класс:

public class Test
{
    public void GetOut(out EmailAddress email)
    {
        try
        {
            Foo(email);
        }
        catch
        {
        }
    }

    public void Foo(EmailAddress email)
    {
    }
}

Нет электронной почты, в GetOutкоторой обычно выдается ошибка:

Выходной параметр 'email' должен быть назначен до того, как элемент управления покинет текущий метод

Однако, если EmailAddress находится в структуре в отдельной сборке, ошибка не создается, и все компилируется нормально.

public struct EmailAddress
{
    #region Constructors

    public EmailAddress(string email)
        : this(email, string.Empty)
    {
    }

    public EmailAddress(string email, string name)
    {
        this.Email = email;
        this.Name = name;
    }

    #endregion

    #region Properties

    public string Email { get; private set; }
    public string Name { get; private set; }

    #endregion
}

Почему компилятор не предписывает, чтобы Email был назначен? Почему этот код компилируется, если структура создается в отдельной сборке, но не компилируется, если структура определена в существующей сборке?

Джонни 5
источник
2
Если вы используете класс, вы должны «создать» новый экземпляр объекта. Это не требуется для структур. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/… (ищите этот текст на этой странице специально: в отличие от классов, структуры могут быть созданы без использования новой оперы)
Dortimer
1
Как только ваша структура Dog получает переменную, она не будет компилироваться :)
Андре Сансон
В этом примере struct Dog{}все хорошо.
Хенк
2
@ johnny5 Тогда покажи пример.
Андре Сансон
1
ОК, это интересно Воспроизводится с приложением Core 3 Console и классом .Standard lib.
Хенк

Ответы:

12

TLDR: это известная давняя ошибка. Я впервые написал об этом в 2010 году:

https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/

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

Почему компилятор не обеспечивает принудительно Emailназначенное значение?

О, это так, в моде. У него просто неверное представление о том, какое условие подразумевает, что переменная определенно назначена, как мы увидим.

Почему этот код компилируется, если структура создается в отдельной сборке, но не компилируется, если структура определена в существующей сборке?

В этом суть ошибки. Ошибка является следствием пересечения того, как компилятор C # выполняет определенную проверку присваивания для структур и как компилятор загружает метаданные из библиотек.

Учти это:

struct Foo 
{ 
  public int x; 
  public int y; 
}
// Yes, public fields are bad, but this is just 
// to illustrate the situation.
void M(out Foo f)
{

Хорошо, на данный момент, что мы знаем? fявляется псевдонимом для переменной типа Foo, поэтому хранилище уже выделено и определенно находится в том состоянии, в котором оно вышло из распределителя хранилища. Если вызывающая сторона поместила значение в переменную, это значение есть.

Что нам нужно? Мы требуем, чтобы это fбыло определенно назначено в любой точке, где контроль уходит Mнормально. Так что вы ожидаете что-то вроде:

void M(out Foo f)
{
  f = new Foo();
}

который устанавливает f.xи f.yих значения по умолчанию. Но как насчет этого?

void M(out Foo f)
{
  f = new Foo();
  f.x = 123;
  f.y = 456;
}

Это также должно быть хорошо. Но, и вот кикер, почему мы должны назначать значения по умолчанию только для того, чтобы выбросить их через мгновение? Средство проверки определенного присваивания в C # проверяет, назначено ли каждое поле ! Это законно:

void M(out Foo f)
{
  f.x = 123;
  f.y = 456;
}

И почему это не должно быть законным? Это тип значения. fпеременная, и она уже содержит допустимое значение типа Foo, так что давайте просто установим поля, и все готово, верно?

Правильно. Так в чем же ошибка?

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

И теперь вы сможете определить причину найденной ошибки. Когда компилятор проверяет, определен ли параметр out, он сравнивает количество известных полей с числом полей, которые были определенно инициализированы, и в вашем случае он знает только о нулевых открытых полях, поскольку метаданные закрытых полей не были загружены. , Компилятор делает вывод: «Обязательные поля равны нулю, инициализированы нулевые поля, все хорошо».

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

И, конечно, ошибка не воспроизводится для закрытых полей структур, которые находятся в исходном коде вашего проекта, потому что, очевидно, компилятор уже имеет информацию о закрытых полях под рукой.

Эрик Липперт
источник
@ johnny5: Вы не должны получать ошибки. Смотрите dotnetfiddle.net/ZEKiUk . Можете ли вы опубликовать простое воспроизведение?
Эрик Липперт
1
Спасибо за скрипку, потому что я определил x и y как свойства вместо членов
Джонни 5
1
@ johnny5: Если вы только что определили обычное свойство стиля C # 1.0, то с точки зрения контролера определенного присваивания это метод, а не поле. Если вы определили автоматическое свойство в стиле C # 3.0+, компилятор знает, что есть частное поле, поддерживающее его; правила для определенного назначения этой вещи были изменены за эти годы, и я не помню сейчас точные правила.
Эрик Липперт
Если вы используете System.TimeSpanвместо этого, ошибки приходят: error CS0269: Use of unassigned out parameter 'email'и error CS0177: The out parameter 'email' must be assigned to before control leaves the current method. Существует только одно нестатическое поле TimeSpan, а именно _ticks. Именно internalна его сборку mscorlib. Эта сборка особенная? То же самое System.DateTime, и его полеprivate
Джеппе Стиг Нильсен
@JeppeStigNielsen: я не знаю, что с этим случилось! Если вы поймете это, пожалуйста, дайте мне знать.
Эрик Липперт
1

Хотя это выглядит как ошибка, в этом есть какой-то смысл.

«Отсутствующая ошибка» появляется только при использовании библиотеки классов. И библиотека классов могла быть написана на другом языке .net, например, VB.Net. «Отслеживание определенных назначений» - это особенность C #, а не фреймворка.

Так что, в целом, я не думаю, что это ошибка, но я не знаю о том, как это можно утверждать.

Хенк Холтерман
источник
Если вы импортируете сборку в C #, даже если структура может лежать в сборке, написанной на другом языке, код, использующий ее, все еще находится в C #, поэтому он не должен использовать отслеживание определенных назначений.
Джонни 5
1
Не обязательно. C # не позволит вам использовать унитализированную (локальную) переменную, но в то же время фреймворк гарантирует, что она будет установлена ​​в 0 ( default(T)). Таким образом, нет нарушения безопасности памяти или чего-то подобного.
Хенк
3
Я могу сделать такое авторитетное заявление. :) Это давно известная ошибка.
Эрик Липперт
1
@EricLippert Спасибо, я надеялся, что ты когда-нибудь увидишь это
Джонни, 5