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


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
}

Чому компілятор не вимагає призначити електронну пошту? Чому цей код складається, якщо структура створена в окремій збірці, але вона не компілюється, якщо структура визначена в існуючій збірці?


2
Якщо ви користуєтесь класом, вам потрібно "створити" екземпляр об'єкта. Це не потрібно для конструкцій. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/… (шукайте цей текст на цій сторінці спеціально: На відміну від класів, структури можна створити екземплярами без використання нового
операто

1
Як тільки ваша структура собак отримає змінну, вона не збирається :)
Андре Сансон

У цьому прикладі, з struct Dog{}, все добре.
Хенк Холтерман

2
@ johnny5 Тоді покажіть приклад.
Андре Сансон

1
Гаразд, це цікаво. Відтворено за допомогою програми Core 3 Console та вкладиш класу .Standard.
Хенк Холтерман

Відповіді:


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 # не завантажує метадані для приватних полів структур, що знаходяться у бібліотеках, на які посилаються . Ці метадані можуть бути величезними , і це сповільнить компілятор за дуже маленьку виграш, щоб кожен раз це завантажувати в пам'ять.

І тепер ви повинні мати можливість вивести причину виявленої помилки. Коли компілятор перевіряє, чи визначений параметр визначено, він порівнює кількість відомих полів з кількістю полів, які були визначені ініціалізовано, і у вашому випадку він знає лише про нульові загальнодоступні поля, оскільки метадані приватного поля не були завантажені . Компілятор робить висновок "нульові поля потрібні, нульові поля ініціалізовані, ми хороші".

Як я вже говорив, ця помилка існує вже не одне десятиліття, і такі люди, як ти, періодично перекривають її та повідомляють про неї. Це нешкідливо, і навряд чи це буде виправлено, тому що виправити це має майже нульову користь, але велика вартість продуктивності.

І звичайно, помилка не дорікає приватним полям структур, які є вихідним кодом у вашому проекті, оскільки, очевидно, компілятор вже має інформацію про приватні поля під рукою.


@ johnny5: Вам не слід помилятися. Дивіться dotnetfiddle.net/ZEKiUk . Чи можете ви опублікувати просте запитання?
Ерік Ліпперт

1
Дякую за загадку, тому що я визначив x і y як властивості замість членів
johnny 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
Jeppe Stig Nielsen

@JeppeStigNielsen: Я не знаю, що з цим! Якщо ви це зрозумієте, будь ласка, дайте мені знати.
Ерік Ліпперт

1

Хоча це виглядає як помилка, але це має певний сенс.

"Помилка відсутності" з'являється лише при використанні бібліотеки класів. А бібліотека класів, можливо, була написана іншою мовою .net, наприклад VB.Net. "Визначальне відстеження призначення" є особливістю C #, а не рамки.

Так що в цілому я не думаю, що це помилка, але я не знаю про авторитетне твердження для цього.


Якщо ви імпортуєте збірку в C #, навіть незважаючи на те, що структура може лежати в збірці, написаній на іншій мові, код, який використовує її, все ще знаходиться в c #, тому не повинно використовувати певне відстеження призначення.
johnny 5

1
Не обов'язково. C # не дозволить вам використовувати неітіалізовану (локальну) змінну, але в той же час рамка гарантує, що вона буде встановлена ​​на 0 ( default(T)). Тож немає порушення безпеки пам’яті чи чогось подібного.
Хенк Холтерман

3
Я можу зробити таку авторитетну заяву. :) Це давній відомий клоп.
Ерік Ліпперт

1
@EricLippert Дякую, я сподівався, що ви побачите це в якийсь момент
johnny 5
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.