Почему параметр Out можно оставить неназначенным в .NET 6, но не в .NET 8 (CS0177)?

Тот же код компилируется в проекте .NET 6, но показывает ошибку компиляции CS0177 в .NET 8. Почему?

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

Поскольку кажется, что он работает в .NET 6...

Приложение работает в .NET 6 с выводом:

Hello, World!
Else: Bad Date Format! 'abc'
01.01.0001

Очевидно, что он использует значение структуры по умолчанию. Но почему-то для .NET 8 это уже не так (пока я не нашел этого в документации, по крайней мере, здесь)

Я тестировал это, создавая по 2 новых консольных проекта в VS для каждой версии, а также меняя целевой фреймворк в обоих проектах с тем же результатом.

CS0177 Выходной параметр «parameter» должен быть присвоен до того, как элемент управления покинет текущий метод.     (без упоминания версий)

(Почему параметр Out можно оставить неназначенным в проектах, ориентированных на .NET Standard? это совсем не помогло, речь идет только о структурах, а не о версиях .net)

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
        TryParse("abc", out DateOnly d);
        Console.WriteLine(d);
        Console.ReadKey();
    }

    public static bool TryParse(string s, out DateOnly dateOnly)
    {
        try
        {
            if (s is not null && s.Length == 8)
            {
                int yyyy = int.Parse(s[0..4]);
                int mm = int.Parse(s[4..6]);
                int dd = int.Parse(s[6..8]);
                var date = new DateOnly(yyyy, mm, dd);
                dateOnly = date;
                return true;
            }
            else
            {
                Console.WriteLine("Else: Bad Date Format! '" + s + "'");
                return false; //CS0177 in .net8
            }
        }
        catch
        {
            Console.WriteLine("Catch: Bad Date Format! '" + s + "'");
            return false; //CS0177 in .net8
        }
    }
}

Вероятно, это ошибка в .NET 6, но я не могу найти для нее соответствующую проблему.

canton7 28.08.2024 11:34

Интересно, что я получаю сообщение об ошибке в .NET 6, если заменяю DateOnly на DateTime.

canton7 28.08.2024 11:36

Альтернативно это может быть ошибка в C#12 (для .NET 8): если связанный вопрос re. structs верен, поскольку DateOnly не имеет доступных полей экземпляра.

Richard 28.08.2024 11:39

@Richard Я не могу воспроизвести на .NET 6 свою собственную структуру, в которой нет доступных полей экземпляра: dotnetfiddle.net/IzoE02

canton7 28.08.2024 11:42

@Sinatr Это также член экземпляра в DateOnly. static не будет репрезентативным

canton7 28.08.2024 11:51

Я подозреваю, что это проблема с эталонными сборками для .NET 6, но я пытаюсь их отследить.

canton7 28.08.2024 11:51

К вашему сведению: воссоздается на C#13 (предварительная версия .NET 9). «Доступные» поля не являются правильным критерием; любое поле в структуре означает отсутствие неявного присваивания. Смотрите мой ответ для ссылок.

Richard 28.08.2024 12:16

@Richard Это не воспроизводится на июньском main на Sharplab: Sharplab.io/…

canton7 28.08.2024 12:17

@canton7 Canton7 Я понятия не имею, на какую среду выполнения/язык нацелена Sharplib. И теперь мне ясно (поработав над спецификацией, см. мой ответ), что отсутствие ошибки в .NET 6 является ошибкой здесь. Учитывая, что .NET 6 почти закончился, я не думаю, что вы увидите исправление.

Richard 28.08.2024 12:23

@Richard Один из разработчиков среды выполнения буквально сказал, что это исправлено в .NET 7 после того, как один из членов команды компилятора сказал, что это произошло из-за неправильной эталонной сборки.

canton7 28.08.2024 12:24

@Ричард Я также не могу воспроизвести с помощью 9.0.100-preview.7

canton7 28.08.2024 12:32

@canton7 Моя реконструкция (использовалась Visual Studio 17.12.0 Preview 1.0): gist.github.com/richardcox13/…

Richard 28.08.2024 12:38

@Richard Когда вы говорите «пересоздает», я полагаю, вы имеете в виду, что у вас нет ошибок? Поскольку проблема в том, что ошибки нет, хотя она должна быть.

canton7 28.08.2024 12:42

@canton7 Обратите внимание. под «пересоздать» я имею в виду, что получаю ошибку (т. е. заново создаю, что ошибка выдается из-за отсутствия сообщения о назначении .NET 6). Я думаю, что вы меняете смысл на противоположный!

Richard 28.08.2024 12:43

@Ричард Верно. Да, вы получите ошибку в .NET 7 и более поздних версиях, поскольку они исправили ссылочные сборки в .NET 7.

canton7 28.08.2024 12:44

@Ричард Я нашел исходную проблему, PR и коммит, который это исправил. Это была проблема с инструментом, который генерирует реф-сборки.

canton7 28.08.2024 12:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
16
904
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это проблема ссылочных сборок в .NET 6. Она исправлена ​​в .NET 7.

В C# структура считается «определенно назначенной», если она не имеет полей, подробности см. в этом ответе.

В .NET 6 ссылочная сборка для System.Runtime содержала определение для DateOnly и TimeOnly, в котором не было полей. Поэтому компилятор рассматривал ее как пустую структуру, которую не нужно было явно назначать.

Подробности см. в этом выпуске GitHub и в другом выпуске . Первоначально это было замечено на SO, в этом вопросе. Это было исправлено путем изменения инструмента, генерирующего ссылочные сборки, чтобы при необходимости включать в такие структуры фиктивное поле.

Git утверждает, что проблема была исправлена ​​в этом коммите, но в диффе она не отображается. Я подозреваю, что где-то там есть злое слияние, которое его скрывает.

Я думаю, вы можете убрать слово «доступный» — я считаю, что это один из тех случаев, когда компилятор заглядывает под капот — IIRC, во многих реферальных сборках есть такие вещи, как private readonly bool dummy; — редактировать: похоже на разные волны «исправления» поскольку это имело разное поведение - слово «доступный» было актуально для пункта 1 здесь, но теперь оно больше не актуально, поскольку (в основном) добавлены недостающие поля, я полагаю: github.com/dotnet/roslyn/issues/29319#issuecomment -414517139

Marc Gravell 28.08.2024 12:07

@MarcGravell Да, хорошая мысль, спасибо, удалено

canton7 28.08.2024 12:11

Не является ли это ошибкой в ​​компиляторе, которую исправили путем изменения правил генерации эталонных сборок, а не проблемой самих эталонных сборок? Здесь сказано, что даже если в структуре есть поле, если это поле ссылочного типа, в другой сборке и приватное, то компилятор все равно его игнорирует.

palapapa 28.08.2024 23:04

@palapapa Интересное совпадение. DateOnly/TimeOnly не имеет поля ссылочного типа, поэтому я не думаю, что это применимо здесь? Также фактом является то, что ссылочные сборки были неправильными в .NET 6 и вообще не содержали никаких полей (в моем ответе есть ссылка), а компилятор следовал спецификации, когда ему был предоставлен тип без полей.

canton7 29.08.2024 06:52

Работаем с последним доступным стандартом C# (я не думаю, что для C# 8 что-то существенное изменилось в спецификации параметров out).

  1. 15.6.2.3.4 Выходные параметры

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

Как и ожидалось, необходимо задать out параметры (об этом сказано дважды в одном абзаце).

  1. 9.4.1 [Определенное присвоение | ] Общие

Состояния определенного присвоения переменных экземпляра переменной struct_type отслеживаются как индивидуально, так и коллективно. В дополнение к правилам, описанным в §9.4.2, §9.4.3 и §9.4.4, к переменным struct_type и их переменным экземпляра применяются следующие правила:

  • Переменная экземпляра считается определенно назначенной, если содержащая ее переменная struct_type считается определенно назначенной.
  • Переменная struct_type считается определенно назначенной, если каждая из ее переменных экземпляра считается определенно назначенной.

В отличие от , на который ссылается Q , здесь нет ничего, что говорит о «доступных полях»: все они должны быть «определенно назначены».

Потому что в некоторых случаях SDK имеет «фасадные» сборки, которые отличаются от среды выполнения (например, DateOnly документировано как наличие в System.Runtime, но реализовано в System.Private.CoreLib). Иногда (например, DateTime) документированная сборка передается реальной реализации и, таким образом, разрешается компилятором, применяющим это правило. В других случаях (и, таким образом, со временем он изменился как деталь реализации) фасад разрешается только загрузчиком и JIT-компилятором во время выполнения, и, таким образом, компилятор не видит внутреннюю часть типа и не может видеть, есть ли поля, которые нужно назначить.

Для очевидной производительности в загрузчике/JIT и, безусловно, в AoT, случай, когда фасад вперед, вероятно, будет более распространенным сейчас, чем в прошлом.

TL/DR: необходимость явного назначения кажется правильной для любой структуры с любыми доступными или нет полями (и почему для структур внутренние изменения могут быть критическими изменениями).

Я думаю, пересылаемые сборки не имеют отношения к времени компиляции? Компилятор полагается только на ссылочные сборки. Так что «фасадные» сборки к этому не имеют отношения. И я не уверен, какое отношение здесь имеет JIT/AOT. Проблема заключается в ошибке компиляции в одной версии .NET, но не в других. Информация о «доступных полях» взята из «Переменная struct_type считается определенно назначенной, если каждая из ее переменных экземпляра считается определенно назначенной» - поэтому всегда назначается структура без полей. Это в сочетании со сборкой ref, в которой не было полей, привело к этой ошибке.

canton7 28.08.2024 13:02

@canton7 для преобразования в собственный машинный код необходима «настоящая» реализация (а не какая-то ссылка, достаточная для компиляции в IL). Это будет JIT-компилятор во время выполнения или при выполнении AoT-компиляции.

Richard 28.08.2024 13:24

Я знаю. Эта ошибка является ошибкой компилятора — она возникает во время компиляции. JIT не является причиной ошибки. Поэтому все, что связано с JIT или типами в сборках, разрешаемыми во время выполнения, не имеет и не может иметь отношения к этому вопросу.

canton7 28.08.2024 13:29

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