Явный и неявный оператор с числовыми типами и неожиданными результатами

Я никогда не делал обширной работы с операторами перегрузки, особенно с неявными и явными преобразованиями.

Тем не менее, у меня есть несколько числовых параметров, которые часто используются, поэтому я создаю структуру как оболочку вокруг числового типа для строгого ввода этих параметров. Вот пример реализации:

public struct Parameter
{
    private Byte _value;
    public Byte Value { get { return _value; } }

    public Parameter(Byte value)
    {
        _value = value;
    }

    // other methods (GetHashCode, Equals, ToString, etc)

    public static implicit operator Byte(Parameter value)
    {
        return value._value;
    }
    public static implicit operator Parameter(Byte value)
    {
        return new Parameter(value);
    }

    public static explicit operator Int16(Parameter value)
    {
        return value._value;
    }
    public static explicit operator Parameter(Int16 value)
    {
        return new Parameter((Byte)value);
    }
}

Поскольку я экспериментировал с моей тестовой реализацией, чтобы освоить явные и неявные операторы, я попытался явно привести Int64 к моему Parameter Типа и, к моему удивлению, это не бросило исключение, и, что еще более удивительно, оно просто сократило число и пошло дальше. Я попытался исключить пользовательский явный оператор, и он по-прежнему вел себя так же.

public void TestCast()
{
    try
    {
        var i = 12000000146;
        var p = (Parameter)i;
        var d = (Double)p;

        Console.WriteLine(i);   //Writes 12000000146
        Console.WriteLine(p);   //Writes 146
        Console.WriteLine(d);   //Writes 146
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);  //Code not reached
    }
}

Поэтому я повторил свой эксперимент с простой Byte вместо моей структуры и имеет такое же точное поведение, так что, очевидно, это ожидаемое поведение, но я думал, что явное приведение, которое приведет к потере данных, вызовет исключение.

2 ответа

Решение

Когда компилятор анализирует явное пользовательское преобразование, ему разрешается помещать явное встроенное преобразование на "любую сторону" (или оба) преобразования. Так, например, если у вас есть пользовательское преобразование из int в Fred, и у вас есть:

int? x = whatever;
Fred f = (Fred)x;

тогда компилятор рассуждает "есть явное преобразование из int в Fred, поэтому я могу сделать явное преобразование из int? в int, а затем преобразовать int в Fred.

В вашем примере есть встроенное явное преобразование из длинного в короткое, и есть определенное пользователем явное преобразование из короткого в Параметр, поэтому преобразование длинного в Параметр допустимо.

То же самое верно для неявных преобразований; компилятор может вставлять встроенные неявные преобразования по обе стороны от пользовательского неявного преобразования.

Компилятор никогда не связывает два пользовательских преобразования.

Правильное построение ваших явных преобразований - трудная задача в C#, и я призываю вас прекратить попытки делать это, пока у вас не будет глубокого и глубокого понимания всей главы спецификации, которая охватывает преобразования.

Для некоторых интересных аспектов связанных преобразований, смотрите мои статьи на эту тему:

http://blogs.msdn.com/b/ericlippert/archive/2007/04/16/chained-user-defined-explicit-conversions-in-c.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/04/18/chained-user-defined-explicit-conversions-in-c-part-two.aspx

Эта цель:

поэтому я создаю структуру как оболочку вокруг числового типа, чтобы строго ввести эти параметры

И этот код:

public static implicit operator Byte(Parameter value)
{
    return value._value;
}
public static implicit operator Parameter(Byte value)
{
    return new Parameter(value);
}

Находятся в полном противоречии. Добавляя двухсторонние неявные операторы, вы отменяете любую безопасность типов, которую может принести оболочка.

Так что отбросьте неявные преобразования. Вы можете изменить их на явные.

Другие вопросы по тегам