Почему компилятор C# не вызывает неявный оператор приведения?

Предположим, у нас есть следующий тип:

struct MyNullable<T> where T : struct
{
    T Value;

    public bool HasValue;

    public MyNullable(T value)
    {
        this.Value = value;
        this.HasValue = true;
    }

    public static implicit operator T(MyNullable<T> value)
    {
        return value.HasValue ? value.Value : default(T);
    }
}

И попробуйте скомпилировать следующий фрагмент кода:

MyNullable<int> i1 = new MyNullable<int>(1);
MyNullable<int> i2 = new MyNullable<int>(2);

int i = i1 + i2;

Этот фрагмент скомпилирован хорошо и без ошибок. i1 и i2 приводится к целому числу и вычисляется сложение.

Но если у нас есть следующий тип:

struct Money
{
    double Amount;
    CurrencyCodes Currency; /*enum CurrencyCode { ... } */

    public Money(double amount, CurrencyCodes currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public static Money operator + (Money x, Money y)
    {
        if (x.Currency != y.Currency)
            // Suppose we implemented method ConvertTo
            y = y.ConvertTo(x.Currency); 

        return new Money(x.Amount + y.Amount, x.Currency);
    }
}

Попробуйте скомпилировать другой фрагмент кода:

MyNullable<Money> m1 = 
   new MyNullable<Money>(new Money(10, CurrenciesCode.USD));
MyNullable<Money> m2 = 
   new MyNullable<Money>(new Money(20, CurrenciesCode.USD));

Money m3 = m1 + m2;

А теперь вопрос, почему компилятор генерирует «ошибка CS0019: оператор '+' не может применяться к операндам типа 'MyNullable <Money>' и 'MyNullable <Money>'»?

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

Ответы 2

Это интересный вопрос ... он работает, например, с Decimal, но не с TimeSpan, которые оба являются правильными типами .NET (в отличие от float и т. д., Которые являются примитивами), и оба имеют оператор +. Любопытный!

Конечно, руку можно крутить:

Money m3 = (Money)m1 + (Money)m2;

И если вы просто используете Nullable<T>, он, конечно, будет работать бесплатно - плюс вы получите поддержку компилятора + времени выполнения (бокса). Есть ли причина не использовать здесь Nullable<T>?

Я посмотрю на спецификацию; тем временем вы можете подумать о переводе оператора на MyNullable<T>; с обычным Nullable<T> компилятор C# предоставляет "расширенные" операторы для тех, которые поддерживаются типом, но вы не можете сделать это самостоятельно. Лучшее, что вы можете сделать, - это предложить все очевидные из них и надеяться, что тип их поддерживает ;-p Чтобы получить доступ к операторам с обобщенными типами, см. здесь, доступный для бесплатной загрузки здесь.

Обратите внимание, что вы, вероятно, захотите применить соответствующие «повышенные» проверки, т. Е.

x + y => (x.HasValue && y.HasValue)
          ? new MyNullable<T>(x.Value + y.Value)
          : new MyNullable<T>();

Обновлять

Другая обработка выглядит как относящаяся к 14.7.4 (ECMA 334 v4) «Оператор сложения», где он предопределен для диапазона типов в том числе decimal (так что это был плохой тест для меня), поскольку в 14.2.4 ( то же самое) «Разрешение перегрузки бинарных операторов», предварительно определенные операторы заслуживают особого упоминания. Однако я не претендую на то, чтобы понять это полностью.

К сожалению, Nullable <Money> тоже не работает, мне кажется, что встроенные примитивы каким-то образом обрабатываются особым образом, хотя я не знаю почему. Я искал определяемые пользователем неявные преобразования в спецификации C#, но, боюсь, я потерялся в технической неразберихе ... Надеюсь, вам это покажется странным ...

Lasse V. Karlsen 26.12.2008 15:08

@lassevk - Nullable <Money> отлично работает - просто результат m1 + m2 равен Nullable <Money>, а не Money

Marc Gravell 26.12.2008 15:20

MyNullable делает все наоборот. Значение типа Nullable неявно недоступно. MyNullable пытается этого добиться.

AnthonyWJones 26.12.2008 15:21

т.е. «Деньги? m3 = m1 + m2;» - компилируется и запускается с результатом 30, как и ожидалось

Marc Gravell 26.12.2008 15:22

@AnthonyWJones - и не зря - что должно возвращать неявное приведение, если оно не имеет значения? Явное приведение (.Value) взрывается (разумно). В противном случае есть GetValueOrDefault ().

Marc Gravell 26.12.2008 15:25

@AnthonyWJones - с комбинацией поднятых операторов (компилятор) и специальных правил бокса (время выполнения) и специальных общих правил (оба) - я не думаю, что вы когда-нибудь сможете сделать MyNullable <T> что близко к Обнуляемый <T>. Это действительно особенный, уникальный случай.

Marc Gravell 26.12.2008 15:26

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

Lasse V. Karlsen 26.12.2008 15:45

@Marc: Да, это была моя точка зрения, Nullable не имеет неявного оператора T, поэтому происходит другая магия. Джон хорошо разбирается в разделе 4.3.3 своей книги.

AnthonyWJones 26.12.2008 15:46

Марк, ты абсолютно прав в том случае, если MyNullable создан только для неявного приведения к T. Потому что такое же выражение с System.Nullable выглядит так: Money? m1, m2; Деньги? m3 = m1.GetValueOrDefault () + m2.GetValueOrDefault (); Удаление вызова GetValueOrDefault () - это мой особый трюк с DSL.

Ed Gomoliako 26.12.2008 16:22
Ответ принят как подходящий

Марк находится в правильных строках - это раздел 7.2.4 в спецификации C# 3.0 - Разрешение перегрузки двоичного оператора.

В основном шаги следующие:

  • Нам нужно разрешить реализацию для «X + Y», где X и Y оба являются MyNullable<Money>.
  • Глядя на раздел 7.2.5 (возможные пользовательские операторы), мы получаем пустой набор, поскольку MyNullable<T> не перегружает +.
  • Еще в 7.2.4 набор операторов-кандидатов представляет собой встроенный набор бинарных операторов для +, то есть int + int, decimal + decimal и т. д.
  • Применяются правила разрешения перегрузки в 7.4.3 тогда. Когда мы выполняем MyNullable<int> + MyNullable<int>, это работает из-за неявного преобразования каждого аргумента в int, но когда мы выполняем MyNullable<Money> + MyNullable<Money>, работает не, потому что Money + Money не входит в набор операторов-кандидатов.

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