Подтвердить что ты не робот

Операция оператора странного преобразования

У меня есть эта структура:

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), так что на самом деле происходит здесь?

4b9b3361

Ответ 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 действительно скомпилировать.