Неявное преобразование в System.Double с пропускаемой структурой через сгенерированные компилятором локальные системы: почему это терпит неудачу?
Учитывая следующее, почему InvalidCastException выбрасывается? Я не понимаю, почему это должно быть за пределами ошибки (это в x86; x64 падает с 0xC0000005 в clrjit.dll).
class Program
{
static void Main(string[] args)
{
MyDouble? my = new MyDouble(1.0);
Boolean compare = my == 0.0;
}
struct MyDouble
{
Double? _value;
public MyDouble(Double value)
{
_value = value;
}
public static implicit operator Double(MyDouble value)
{
if (value._value.HasValue)
{
return value._value.Value;
}
throw new InvalidCastException("MyDouble value cannot convert to System.Double: no value present.");
}
}
}
Вот CIL, сгенерированный для Main()
:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 3
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<valuetype Program/MyDouble> my,
[1] bool compare,
[2] valuetype [mscorlib]System.Nullable`1<valuetype Program/MyDouble> CS$0$0000,
[3] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0001)
L_0000: nop
L_0001: ldloca.s my
L_0003: ldc.r8 1
L_000c: newobj instance void Program/MyDouble::.ctor(float64)
L_0011: call instance void [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::.ctor(!0)
L_0016: nop
L_0017: ldloc.0
L_0018: stloc.2
L_0019: ldloca.s CS$0$0000
L_001b: call instance bool [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::get_HasValue()
L_0020: brtrue.s L_002d
L_0022: ldloca.s CS$0$0001
L_0024: initobj [mscorlib]System.Nullable`1<float64>
L_002a: ldloc.3
L_002b: br.s L_003e
L_002d: ldloca.s CS$0$0000
L_002f: call instance !0 [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::GetValueOrDefault()
L_0034: call float64 Program/MyDouble::op_Implicit(valuetype Program/MyDouble)
L_0039: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0)
L_003e: stloc.3
L_003f: ldloca.s CS$0$0001
L_0041: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_0046: call float64 Program/MyDouble::op_Implicit(valuetype Program/MyDouble)
L_004b: conv.r8
L_004c: ldc.r8 0
L_0055: bne.un.s L_0060
L_0057: ldloca.s CS$0$0001
L_0059: call instance bool [mscorlib]System.Nullable`1<float64>::get_HasValue()
L_005e: br.s L_0061
L_0060: ldc.i4.0
L_0061: stloc.1
L_0062: ret
}
Обратите внимание на строки 0x2D - 0x3E в IL. Получает MyDouble?
экземпляр, звонки GetValueOrDefault
на нем вызывает неявный оператор, а затем оборачивает результат в Double?
и сохраняет его в сгенерированном компилятором CS$0$0001
местный. В строках от 0x3F до 0x55 мы получаем CS$0$0001
значение, развернуть через GetValueOrDefault
и затем сравните с 0... НО ПОДОЖДИТЕ МИНУТУ! Что это за дополнительный вызов MyDouble::op_Implicit
делать по линии 0х46?
Если мы отладим программу на C#, мы действительно увидим 2 вызова implicit operator Double(MyDouble value)
, и это второй вызов, который не удается, так как value
не инициализируется.
Что здесь происходит?
2 ответа
Это явно ошибка компилятора C#. Спасибо, что обратили на это мое внимание.
Кстати, это плохая практика иметь пользовательский оператор неявного преобразования, который выдает исключение; в документации говорится, что неявные преобразования должны быть теми, которые никогда не генерируются. Вы уверены, что не хотите, чтобы это было явное преобразование?
Во всяком случае, вернемся к ошибке.
Сообщения об ошибках в C# 3 и 4, но не в C# 2. Это означает, что это была моя ошибка. Я, вероятно, вызвал ошибку, когда я переделал пользовательский код неявного поднятого оператора, чтобы заставить его работать с лямбдами дерева выражений. Извини за это! Этот код очень хитрый, и, видимо, я не проверил его адекватно.
Код должен делать следующее:
Во-первых, разрешение перегрузки пытается определить значение ==. Лучшим оператором ==, для которого оба аргумента являются действительными, является поднятый оператор, который сравнивает два обнуляемых типа double Поэтому его следует проанализировать как:
Boolean compare = (double?)my == (double?)0.0;
(Если вы напишите такой код, значит, в C# 3 и 4 все будет правильно.)
Значение поднятого оператора ==:
- оценить оба аргумента
- если оба равны нулю, то результат верен - ясно, что это не может произойти в этом случае
- если один равен нулю, а другой нет, то результат ложен
- если оба не равны нулю, то оба развертываются до двойного и сравниваются как двойные.
Теперь вопрос: "Как правильно оценить левую сторону?"
У нас здесь отменен пользовательский оператор преобразования из MyDouble? удвоить?. Правильное поведение:
- Если "my" равно нулю, то результатом будет нулевой дубль?.
- Если "my" не равно нулю, то результатом является пользовательское преобразование my.Value в double, а затем преобразование этого double в double?.
Очевидно, что-то идет не так в этом процессе.
Я внесу ошибку в нашу базу данных, но любое исправление, вероятно, пропустит крайний срок для изменений, которые внесут его в следующий пакет обновления. Я бы искал обходные пути на вашем месте. Снова извиняюсь за ошибку.
Для меня это похоже на ошибку компилятора. IL предполагает, что компилятор генерирует код для преобразования MyDouble? в дабл с оператором преобразования, затем в дабл?. Но занимает пикирование, когда снова использует оператор преобразования для этого двойника? Это плохо, неверный тип аргумента. И в этом нет необходимости.
Эта статья обратной связи напоминает эту ошибку. Уже более 6 лет это должно быть сложной частью компилятора. Я действительно представляю это.