Эквивалентные неявные операторы:почему они легальны?

StackOverflow https://stackoverflow.com/questions/3561619

Вопрос

Обновление!

Смотрите мое описание части спецификации C # ниже;Я думаю, что, должно быть, я чего-то не понимаю, потому что для я похоже, что поведение, которое я описываю в этом вопросе, на самом деле нарушает спецификацию.

Обновление 2!

Хорошо, после дальнейших размышлений и на основе некоторых комментариев, я думаю, что теперь понимаю, что происходит.Слова "исходный тип" в спецификации относятся к преобразуемому типу От -- то есть, Type2 в моем примере ниже - это просто означает, что компилятор способен сузить список кандидатов до двух определенных операторов (поскольку Type2 является исходным типом для обоих).Однако это не может еще больше сузить выбор.Итак, ключевыми словами в спецификации (применительно к этому вопросу) являются "тип источника", который я ранее неверно истолковал (я думаю) как означающий "объявляющий тип".


Оригинальный Вопрос

Допустим, у меня есть определенные эти типы:

class Type0
{
    public string Value { get; private set; }

    public Type0(string value)
    {
        Value = value;
    }
}

class Type1 : Type0
{
    public Type1(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type1's operator.");
    }
}

class Type2 : Type0
{
    public Type2(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type2's operator.");
    }
}

Тогда скажи, что я делаю это:

Type2 t2 = new Type2("B");
Type1 t1 = t2;

Очевидно, что это двусмысленно, поскольку неясно, какой именно implicit следует использовать оператора.Мой вопрос в том, что, поскольку я не вижу Любой способ разрешить эту двусмысленность (не похоже, что я могу выполнить какое-то явное приведение, чтобы уточнить, какую версию я хочу), и все же приведенные выше определения классов компилируются -- почему компилятор допускает эти совпадения implicit операторы вообще?


Вскрытие

Хорошо, я собираюсь пройтись по отрывку из спецификации C #, цитируемому Хансом Пассантом, в попытке разобраться в этом.

Найдите набор типов, D, из которых будут рассматриваться определяемые пользователем операторы преобразования.Этот набор состоит из S (если S - класс или структура), базовых классов S (если S - класс) и T (если T - класс или структура).

Мы обращаемся От Type2 (S) Для Type1 (T).Так что, похоже, что здесь D будет включать все три типа в примере: Type0 (потому что это базовый класс S), Type1 (T) и Type2 (S).

Найдите набор применимых определяемых пользователем операторов преобразования, U.Этот набор состоит из определяемых пользователем неявных операторов преобразования, объявленных классами или структурами в D, которые преобразуют из типа, охватывающего S, в тип, охватываемый T.Если U равно пусто, преобразование не определено и возникает ошибка времени компиляции.

Итак, у нас есть два оператора, удовлетворяющих этим условиям.Версия, заявленная в Type1 соответствует требованиям, потому что Type1 находится в D и он преобразуется из Type2 (что, очевидно, включает в себя S) к Type1 (который, очевидно, охватывается T).Версия в Type2 также соответствует требованиям точно по тем же причинам.Итак U включает в себя оба этих оператора.

Наконец, что касается поиска наиболее конкретного "типа источника" SX из операторов в U:

Если какой-либо из операторов в U преобразуется из S, то SX равен S.

Сейчас, и то , и другое операторы в U конвертировать из S -- итак , это говорит мне о том , что SX является S.

Разве это не означает, что Type2 следует ли использовать версию?

Но подождите!Я в замешательстве!

Разве я не мог Только определенный Type1версия оператора, и в этом случае единственным оставшимся кандидатом будет Type1версия пользователя, и все же в соответствии со спецификацией SX было бы Type2?Это похоже на возможный сценарий, в котором спецификация предписывает что-то невозможное (а именно, что преобразование, объявленное в Type2 должен использоваться, когда на самом деле его не существует).

Это было полезно?

Решение

На самом деле мы не хотим, чтобы это была ошибка времени компиляции только для определения преобразований, которые мог бы вызвать двусмысленность.Предположим, что мы изменяем Type0 для сохранения double , и по какой-то причине мы хотим предоставить отдельные преобразования в целое число со знаком и целое число без знака.

class Type0
{
    public double Value { get; private set; }

    public Type0(double value)
    {
        Value = value;
    }

    public static implicit operator Int32(Type0 other)
    {
        return (Int32)other.Value;
    }

    public static implicit operator UInt32(Type0 other)
    {
        return (UInt32)Math.Abs(other.Value);
    }

}

Это отлично компилируется, и я могу использовать оба преобразования с

Type0 t = new Type0(0.9);
int i = t;
UInt32 u = t;

Тем не менее, это ошибка компиляции, которую нужно попробовать float f = t потому что любое из неявных преобразований может быть использовано для получения целочисленного типа, который затем может быть преобразован в float.

Мы хотим, чтобы компилятор жаловался на эти более сложные неоднозначности только тогда, когда они действительно используются, поскольку мы хотели бы, чтобы приведенный выше Тип0 был скомпилирован.Для обеспечения согласованности более простая двусмысленность также должна вызывать ошибку в тот момент, когда вы ее используете, а не при ее определении.

Редактировать

Поскольку Ганс удалил свой ответ, в котором цитировалась спецификация, вот краткий обзор части спецификации C #, которая определяет, является ли преобразование неоднозначным, определив U как набор всех преобразований, которые могли бы выполнить эту работу:

  • Найдите наиболее конкретный тип источника, SX, из операторов в U:
    • Если какой-либо из операторов в U преобразуется из S, то SX равен S.
    • В противном случае, SX является наиболее распространенным типом в объединенном наборе целевых типов операторов в U.Если не удается найти наиболее охватываемый тип, то преобразование является неоднозначным и возникает ошибка времени компиляции.

Перефразируя, мы предпочитаем преобразование, которое преобразует непосредственно из S, в противном случае мы предпочитаем тип, в который "проще всего" преобразовать S.В обоих примерах у нас есть два доступных преобразования из S.Если бы не было обращений из Type2, мы бы предпочли преобразование из Type0 более одного из object.Если ни один тип, очевидно, не является лучшим выбором для конвертации, то здесь мы потерпим неудачу.

  • Найдите наиболее конкретный целевой тип, TX, из операторов в U:
    • Если какой-либо из операторов в U преобразуется в T, то TX равен T.
    • В противном случае, TX является наиболее всеобъемлющим типом в объединенном наборе целевых типов операторов в U.Если не удается найти ни одного наиболее всеобъемлющего типа, то преобразование является неоднозначным и возникает ошибка времени компиляции.

Опять же, мы предпочли бы преобразовать непосредственно в T, но мы остановимся на типе, который "проще всего" преобразовать в T.В примере Дэна у нас есть два доступных преобразования в T.В моем примере возможными целями являются Int32 и UInt32, и ни один из них не подходит лучше, чем другой, так что именно здесь преобразование завершается неудачей.У компилятора нет способа узнать, является ли float f = t означает float f = (float)(Int32)t или float f = (float)(UInt32)t.

  • Если U содержит ровно один определенный пользователем оператор преобразования, который преобразует из SX в TX, то это наиболее конкретный оператор преобразования.Если такого оператора не существует или если существует более одного такого оператора, то преобразование является неоднозначным и возникает ошибка времени компиляции.

В примере Дэна мы терпим неудачу здесь, потому что у нас осталось два преобразования из SX в TX.У нас не могло бы быть конверсий из SX в TX, если бы мы выбрали разные конверсии при выборе SX и TX.Например, если бы у нас был Type1a производный от Type1, тогда у нас могли бы быть преобразования из Type2 Для Type1a и от Type0 Для Type1 Это все равно дало бы нам SX = Type2 и TX = Type1, но на самом деле у нас нет никакого преобразования из Type2 в Type1.Это нормально, потому что это действительно неоднозначно.Компилятор не знает, следует ли преобразовать Type2 в Type1a, а затем преобразовать в Type1, или сначала преобразовать в Type0, чтобы он мог использовать это преобразование в Type1.

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

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

Есть преимущество в том, чтобы не пытаться запретить то, что невозможно запретить, поскольку это способствует ясности и последовательности (и в этом есть урок для законодателей).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top