Почему целое число, допускающее значение NULL, не может быть неявно преобразовано в целое число? Техническая причина или выбор дизайна?

В C# нет неявного преобразования типа int? в тип int.

Я определил следующий неявный оператор

namespace System
{
    public partial struct Int32
    {
        public static implicit operator Int32(int? v)
        {
            return (Int32)(v ?? 0);
        }
    }
}

Что позволяет мне скомпилировать следующий код

int? nullableInt = 0;
Int32 regularInt = nullableInt;

но если я определяю regularInt как int вместо Int32, я получаю следующую ошибку

Cannot implicitly convert type 'int?' to 'int'. An explicit conversion exists (are you missing a cast?)

Я ожидал, что int и Int32 будут взаимозаменяемыми, но язык C# явно не был создан с учетом этой функциональности.

Есть ли техническая причина невозможности определить эту операцию, это решение принято для предотвращения потенциального запаха кода?

Я знаю, что определение такого неявного оператора может привести к очень неожиданному поведению, поскольку преобразование значения null в целое число 0 не во всех ситуациях имеет смысл. Этот вопрос больше о том, «почему это нельзя сделать», чем о том, «почему делать это — плохая идея В самом деле».

Потому что это не имеет смысла. null не равно 0. null означает, что фактическое значение неизвестно. Это означает, что неявный оператор — плохая идея. Почему произвольное преобразование в 0? Почему не int.MinValue или -1 ?

Panagiotis Kanavos 28.05.2019 17:42

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

p.s.w.g 28.05.2019 17:43

@RenéVogt «int и Int32 одинаковы», а не в показанном здесь коде, это не так.

Servy 28.05.2019 17:43

Компилятор опередил вас, предпочтя выдать диагностику этой очень распространенной ошибки, прежде чем рассматривать оператор преобразования. Возможно, это можно считать ошибкой, но вполне вероятно, что Эрик Липперт не согласен :) В остальном хорошая демонстрация того, что int - это не просто «псевдоним» для Int32.

Hans Passant 28.05.2019 17:45

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

Servy 28.05.2019 17:52

Мой компилятор выдает CS0436: «тип Int32 в ThisTerribleIdea.cs конфликтует с импортированным типом int в mscorlib, ...», что ясно демонстрирует вашу проблему. (И почему создание предупреждений об ошибках, как правило, является хорошей идеей в C#.) Конечно, система Int32 не partial с самого начала, так что это никогда не сработает. Системные типы не являются «открытыми» в С#, и вы не можете добавлять что-либо. (Такой язык, как F#, относится к этому более спокойно.)

Jeroen Mostert 28.05.2019 17:53

Добавляя комментарий @JeroenMostert, partial — это вещь времени компиляции, и она работает только для двух типов в одной сборке. Компилятор использует оба частичных определения при компиляции типа. Это не способ добавления вещей к произвольным типам.

canton7 28.05.2019 17:54

Частичные типы @JeroenMostert комбинируются только с другими частичными версиями этого типа из той же сборки, поэтому даже если системный Int32 был частичным, это не изменило бы того, что делает этот код.

Servy 28.05.2019 17:54

@HansPassant Согласно спецификация языка, int — это псевдоним для Int32. У вас есть ссылка на ваше заявление?

Kenneth K. 28.05.2019 17:55

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

Panagiotis Kanavos 28.05.2019 17:56

@КеннетК. Ганс прав. int не является только что псевдонимом. Это ключевое слово C#, которое компилятор сопоставляет с System.Int32. Существуют различные крайние случаи, когда разница вызывает проблемы, хотя и это один из них

Panagiotis Kanavos 28.05.2019 18:01

@PanagiotisKanavos Я не понимаю вашу ссылку. Даже там написано, что int — это псевдоним для Int32 («В C# int — это просто псевдоним для System.Int32, поддерживаемый компилятором C#».). В нем также говорится, что int — это нет псевдоним для CLR int32, что, я думаю, не то, что говорится в этой ветке.

Kenneth K. 28.05.2019 18:14
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
12
309
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Код, который у вас есть, не добавляет неявное преобразование из .NET nullable int в .NET int. Он создает совершенно новый тип с именем Int32 в пространстве имен System, но, поскольку он находится в другой сборке, чем Core.dll, это другой тип. (Взгляните на typeof(int).FullName и typeof(int32).FullName, чтобы увидеть это.)

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

Вы не можете создавать неявные (или явные) преобразования для типов вне определения одного из этих типов, и поскольку вы не можете получить доступ к источнику ни Nullable, ни .NET Int32, вы не можете добавить неявное преобразование.

Я не знал, что должен быть в определяющей сборке, я предполагал, что простое написание кода в правильном пространстве имен поможет. Спасибо, что доказали невозможность этой глупой идеи (не волнуйтесь, люди, я на самом деле не собираюсь это реализовывать, я просто люблю исследовать вещи. Это хороший способ учиться).

Mathieu VIALES 28.05.2019 18:11

@mat Я понимаю вашу точку зрения, хотя это было бы довольно авантюрно использовать в повседневной практике, поскольку неявное значение по умолчанию null на 0 может привести к некоторым трудным проблемам с поиском.

LuckyLikey 28.05.2019 18:17

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

Пример из прошлого

// Implicit conversion. A long can
// hold any value an int can hold, and more!
int num = 2147483647; 
long bigNum = num;

В вашем случае int или Int32 можно безопасно преобразовать в int?, но не наоборот.

То же самое относится и к иерархии типов

class A {}

class B : A {}

class C : A {}

A var1 = new A(); // Compiles
A var2 = new B(); // Compiles (implicit cast)
B var3 = new B(); // Compiles

// need for explicit cast because instance2 can could also be A or C
B var4 = (B) instance2;

// Throws InvalidCastException
C var5 = (C) instance2;

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

LuckyLikey 28.05.2019 18:14

Поскольку значение по умолчанию для int равно 0, но int? по умолчанию имеет нулевое значение, которое не является допустимым значением int и приведет к исключению.

Например, интервал х; // результат объявления 0 инт? Икс; // результаты объявления равны нулю (недопустимое целочисленное значение)

Спасибо за ваше время, но это не отвечает на ваш вопрос, и ваше утверждение о том, что int x имеет значение 0, неверно. int x создает неинициализированную переменную. Попробуйте выполнить любую арифметическую операцию с этой переменной x, и вы получите ошибку компилятора.

Mathieu VIALES 28.05.2019 18:15

@MathieuVIALES Если x является членом класса или структуры, вам не нужна явная инициализация. Если это намерение Джоша, он должен так и сказать, чтобы избежать недоразумений.

15ee8f99-57ff-4f92-890c-b56153 28.05.2019 18:18

Привет, @EdPlunkett, рад тебя видеть :) Хороший момент, хотя он мог бы указать это, добавив ключевое слово private и используя соглашение об именах C# по умолчанию.

LuckyLikey 28.05.2019 18:21

Лучшим примером может быть int defaultInt = default(int); int? defaultNullableInt = default(int?);

Rufus L 28.05.2019 21:30

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