Операция оператора странного преобразования
У меня есть эта структура:
public struct MyValue
{
public string FirstPart { get; private set; }
public string SecondPart { get; private set; }
public static implicit operator MyValue(string fromInput)
{ // first breakpoint here.
var parts = fromInput.Split(new[] {'@'});
return new MyValue(parts[0], parts[1]);
}
public static implicit operator string(MyValue fromInput)
{ // second breakpoint here.
return fromInput.ToString();
}
public override string ToString()
{
return FirstPart + "@" + SecondPart;
}
public MyValue(string firstPart, string secondPart) : this()
{
this.FirstPart = firstPart;
this.SecondPart = secondPart;
}
}
И я установил точки останова, как указано выше.
Затем я делаю это:
var first = new MyValue("first", "second");
if (first == (MyValue) null) throw new InvalidOperationException();
Я наблюдаю какое-то странное поведение, когда он входит в if (first == (MyValue) null)
: вторая точка останова по какой-то причине попадает. Почему он пытается преобразовать MyValue
в строку для простого сравнения равенства?
Затем, если я дам код продолжить, он попадает в первую точку останова, и теперь мне интересно, почему он пытается преобразовать строку (значение null
, несмотря на то, что я явно лидировал null
в MyValue
) в MyValue
? Строки не должны быть задействованы при использовании выражения типа if (first == (MyValue) null)
, так что на самом деле происходит здесь?
Ответы
Ответ 1
Был занят комментарием, и стало ясно, в чем проблема.
Компилятор С# не может скомпилировать (MyStruct) null
, но в вашем случае это делает.
Это происходит, поскольку у вас есть неявный оператор из ссылочного типа (этот случай string
), где null
отлично действует.
Я думаю, теперь вы можете следить за тем, почему он выполняется так, как вы видите:)
PS: Это хороший пример, почему "неявные" неявные операторы вообще не приветствуются.
Ответ 2
Чтобы завершить ответ @leppies, это код вызова (режим выпуска):
public void X()
{
var first = new MyValue("first", "second");
if (first == (MyValue) null) throw new InvalidOperationException();
}
Что на самом деле скомпилируется:
public void X()
{
if (new MyValue("first", "second") == null)
{
throw new InvalidOperationException();
}
}
И это испускаемый ИЛ для вызова:
// Methods
.method public hidebysig
instance void X () cil managed
{
// Method begins at RVA 0x20dc
// Code size 45 (0x2d)
.maxstack 8
IL_0000: ldstr "first"
IL_0005: ldstr "second"
IL_000a: newobj instance void MyValue::.ctor(string, string)
IL_000f: call string MyValue::op_Implicit(valuetype MyValue)
IL_0014: ldnull
IL_0015: call valuetype MyValue MyValue::op_Implicit(string)
IL_001a: call string MyValue::op_Implicit(valuetype MyValue) <--- This!
IL_001f: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0024: brfalse.s IL_002c
IL_0026: newobj instance void [mscorlib]System.InvalidOperationException::.ctor()
IL_002b: throw
IL_002c: ret
} // end of method C::X
Как вы можете видеть, после создания нового экземпляра MyValue
операция IL_001a
вызывает неявное преобразование в string
, поскольку - единственная возможность для компилятора сделать сравнение типа значения на null
действительно скомпилировать.