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'
Здравствуйте, 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.
Здравствуйте, 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), и, поскольку, преобразование неоднозначно, происходит ошибка компиляции.
Здравствуйте, 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 там целая глава на это.
Спасибо за качественный ответ
Здравствуйте, 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), и, поскольку, преобразование неоднозначно, происходит ошибка компиляции.
Спасибо