Не понимаю приведение типов и/или поиск членов в C#
От: minorearth  
Дата: 22.07.10 19:44
Оценка:
        class Foo
        {
            private long mData;

            public static implicit operator uint( Foo c )
            {
                return ( uint )c.mData;
            }
            
            public static implicit operator int( Foo c )
            {
                return ( int )c.mData;
            }

            public static implicit operator ushort( Foo c )
            {
                return (ushort)c.mData;
            }

            public static implicit operator sbyte( Foo c )
            {
                return ( sbyte )c.mData;
            }
        }


Почему при вызове:
Console.Write(new Foo());

Будет вызвано неявное преобразование:
public static implicit operator int( Foo c )


А если его закоментить то будет ошибка компиляции:
Ambiguous user defined conversions 'ConsoleModule.ConsoleUI.Foo.implicit operator sbyte(ConsoleModule.ConsoleUI.Foo)' and 'ConsoleModule.ConsoleUI.Foo.implicit operator ushort(ConsoleModule.ConsoleUI.Foo)' when converting from 'ConsoleModule.ConsoleUI.Foo' to 'int'
Re: Не понимаю приведение типов и/или поиск членов в C#
От: nikov США http://www.linkedin.com/in/nikov
Дата: 23.07.10 07:28
Оценка: 18 (4)
Здравствуйте, minorearth, Вы писали:

M>Почему при вызове:

M>
Console.Write(new Foo());

M>Будет вызвано неявное преобразование:
M>
public static implicit operator int( Foo c )


M>А если его закоментить то будет ошибка компиляции:

M>Ambiguous user defined conversions 'ConsoleModule.ConsoleUI.Foo.implicit operator sbyte(ConsoleModule.ConsoleUI.Foo)' and 'ConsoleModule.ConsoleUI.Foo.implicit operator ushort(ConsoleModule.ConsoleUI.Foo)' when converting from 'ConsoleModule.ConsoleUI.Foo' to 'int'

Полный анализ достаточно длинный, поэтому скажу про основные моменты, а потом, если что-то непонятно, опишу в деталях. Интересно, что ReSharper не сообщает об ошибке, если закомментировать implicit operator int(Foo). Это известный баг, я обнаруживал его на похожих примерах, и ниже скажу, с чем он связан.

Метод Console.Write перегруженный, поэтому надо выбрать перегрузки, которые применимы к данному аргументу, а потом выбрать из них лучшую, если она есть. Нас будут особенно интересовать методы WriteLine(int) и WriteLine(uint), т.к. остальные (принимающие object, decimal и т.д.), даже если будут применимыми, заведомо не окажутся лучшими, так как принимают слишком общие типы (не буду расписывать это подробно). Ясно, что неявные преобразования от типа Foo к типам int и uint, если существуют, то являются user-defined преобразованиями. Они регулируются разделом спецификации C# 6.4.4 User-defined implicit conversions. Смысл этого раздела в том, что мы должны собрать определения user-defined преобразований с подходящей сигнатурой, а потом выбрать из них лучшее. Если есть преобразование, преобразующее в точности в требуемый тип, то оно является лучшим. Иначе лучшим является преобразование, преобразующее в самый "близкий" к требуемому тип (пишу несколько упрощённо, точные определения есть в том разделе). Если не удается найти определения оператора с подходящей сигнатурой, то user-defined преобразования не существует, а значит, соответствующий метод WriteLine не будет применимым. Если существует несколько операторов, но не удается найти единственный самый "близкий" к требуемому тип, то преобразование неоднозначно, но (внимание!) оно всё равно существует, а значит, соответствующий метод WriteLine будет применимым и будет участвовать в дальнейшем выборе наилучшего. Если в качестве наилучшего метода будет выбрана перегрузка, требующая выполнения этого неоднозначного преобразования, то мы получим ошибку компиляции. К сожалению, это поведение при наличии неоднозначного преобразования не очень ясно прописано в спецификации, и неверно реализовано в РеШарпере (он считает равнозначными ситуации, когда преобразование неоднозначно, и когда его вообще нет). И, кстати, поведение компилятора C# отличается от компилятора VB.NET, который также приравнивает неоднозначные преобразование к несуществующим.

Ну а теперь всё понятно: когда implicit operator int(Foo) присутствует, то преобразование Foo->int существует и однозначно, поэтому метод WriteLine(int) является применимым, лучшим, и вызывается успешно (метод WriteLine(uint) тоже применим, но не является лучшим). Когда оператор закомментирован, то преобразование Foo->int по-прежнему существует, но уже неоднозначно (так как из подходящих типов ushort и sbyte ни один не является самым "близким" к int), поэтому метод WriteLine(int) все равно является применимым, лучшим (знаковые целые выигрывают у беззнаковых, согласно 7.5.3.5 Better conversion target), но не может быть успешно вызван, так как требует неоднозначного преобразования — поэтому мы и получаем ошибку. Метод WriteLine(uint) мог бы быть вызван успешно, но не вызывается, так как не выиграл в overload resolution.
Re: Не понимаю приведение типов и/или поиск членов в C#
От: k.o. Россия  
Дата: 23.07.10 07:50
Оценка: 5 (2)
Здравствуйте, minorearth, Вы писали:

M>Почему при вызове:

M>
Console.Write(new Foo());

M>Будет вызвано неявное преобразование:
M>
public static implicit operator int( Foo c )


Во-первых, нужно выбрать какой из методов Console.Write вызвать, подробности можно посмотреть в спецификации, пункты 7.4.3.2-7.4.3.4. Если кратко, то преобразование в int "лучше" чем в uint, поэтому выбирается Console.Write(int).

Теперь нужно преобразовать Foo в int, поскольку у нас есть соответсвующий оператор он и используется.

M>А если его закоментить то будет ошибка компиляции:

M>Ambiguous user defined conversions 'ConsoleModule.ConsoleUI.Foo.implicit operator sbyte(ConsoleModule.ConsoleUI.Foo)' and 'ConsoleModule.ConsoleUI.Foo.implicit operator ushort(ConsoleModule.ConsoleUI.Foo)' when converting from 'ConsoleModule.ConsoleUI.Foo' to 'int'

По прежнему, при разрешении перегрузки выбирается Console.Write(int). Однако теперь у нас 2 одинаково подходящих преобразования из Foo в int (см. 6.4.4), и, поскольку, преобразование неоднозначно, происходит ошибка компиляции.
Re[2]: Не понимаю приведение типов и/или поиск членов в C#
От: minorearth  
Дата: 23.07.10 09:13
Оценка:
Здравствуйте, nikov, Вы писали:

N>Здравствуйте, minorearth, Вы писали:


M>>Почему при вызове:

M>>
Console.Write(new Foo());

M>>Будет вызвано неявное преобразование:
M>>
public static implicit operator int( Foo c )


M>>А если его закоментить то будет ошибка компиляции:

M>>Ambiguous user defined conversions 'ConsoleModule.ConsoleUI.Foo.implicit operator sbyte(ConsoleModule.ConsoleUI.Foo)' and 'ConsoleModule.ConsoleUI.Foo.implicit operator ushort(ConsoleModule.ConsoleUI.Foo)' when converting from 'ConsoleModule.ConsoleUI.Foo' to 'int'

N>Полный анализ достаточно длинный, поэтому скажу про основные моменты, а потом, если что-то непонятно, опишу в деталях. Интересно, что ReSharper не сообщает об ошибке, если закомментировать implicit operator int(Foo). Это известный баг, я обнаруживал его на похожих примерах, и ниже скажу, с чем он связан.


N>Метод Console.Write перегруженный, поэтому надо выбрать перегрузки, которые применимы к данному аргументу, а потом выбрать из них лучшую, если она есть. Нас будут особенно интересовать методы WriteLine(int) и WriteLine(uint), т.к. остальные (принимающие object, decimal и т.д.), даже если будут применимыми, заведомо не окажутся лучшими, так как принимают слишком общие типы (не буду расписывать это подробно).


Блин ... этож надо быть таки тупым... я только сейчас понял что все это время курил спеки 3 версии ... в 4 там целая глава на это.
Спасибо за качественный ответ
Re[2]: Не понимаю приведение типов и/или поиск членов в C#
От: minorearth  
Дата: 23.07.10 09:18
Оценка:
Здравствуйте, k.o., Вы писали:

KO>Здравствуйте, minorearth, Вы писали:


M>>Почему при вызове:

M>>
Console.Write(new Foo());

M>>Будет вызвано неявное преобразование:
M>>
public static implicit operator int( Foo c )


KO>Во-первых, нужно выбрать какой из методов Console.Write вызвать, подробности можно посмотреть в спецификации, пункты 7.4.3.2-7.4.3.4. Если кратко, то преобразование в int "лучше" чем в uint, поэтому выбирается Console.Write(int).


KO>Теперь нужно преобразовать Foo в int, поскольку у нас есть соответсвующий оператор он и используется.


M>>А если его закоментить то будет ошибка компиляции:

M>>Ambiguous user defined conversions 'ConsoleModule.ConsoleUI.Foo.implicit operator sbyte(ConsoleModule.ConsoleUI.Foo)' and 'ConsoleModule.ConsoleUI.Foo.implicit operator ushort(ConsoleModule.ConsoleUI.Foo)' when converting from 'ConsoleModule.ConsoleUI.Foo' to 'int'

KO>По прежнему, при разрешении перегрузки выбирается Console.Write(int). Однако теперь у нас 2 одинаково подходящих преобразования из Foo в int (см. 6.4.4), и, поскольку, преобразование неоднозначно, происходит ошибка компиляции.


Спасибо
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.