Двойное литье требуется для преобразования из int как объекта в float

Вот и я столкнулся со странным. У меня есть функция, которая возвращает object. В конкретном случае я точно знаю, что объект является int, но другая функция, вызывающая эту функцию, требует float. Единственный способ, которым я мог заставить это работать, заключался в следующем:

private object parser(string expr) {...}

private float parseFloat(string expr)
{
    ...
    object result = parser(expr);
    if (result is int)
        return (float)(int)result;
    else
        return (float)result;
}

Без этой проверки типов я вообще не мог заставить его работать, хотя я знал, что результатом функции синтаксического анализатора в этом случае будет int. (Вы могли видеть это в отладчике.) Я бы предпочел, чтобы строка шла просто:

private float parseFloat(string expr)
{
    ...
    return (float)parser(expr);
}

(Проверка типов выполняется заранее, и parseFloat никогда не следует вызывать с выражением, которое не будет оцениваться как float или int.) Есть ли какая-то причина, по которой необходимо дважды приводить эту переменную? Я, очевидно, не хочу использовать двойное приведение во всех случаях, потому что если бы возврат из parser был float, он сначала усек бы его до int, а мы этого не хотим. (И да, я пытался заменить float и int на Single и Int32 и т. д. Никакой разницы.)

Я видел этот вопрос, но это зависит от предварительного знания типа , и единственное решение, которое он предлагает, - это трюк с двойным приведением типов, который в моем случае усекает floats, если я сначала не проверю тип, что также требует дополнительной локальной переменной для хранения результата. Есть ли способ избежать этого дополнительного шага?


person Darrel Hoffman    schedule 07.07.2013    source источник
comment
Почему бы вам не предоставить ParseFloat, если вы точно знаете, что это один?   -  person Tim Schmelter    schedule 08.07.2013
comment
Вот для чего... Выражение, переданное в ParseFloat, является либо литералом с плавающей запятой (который обрабатывается в коде ...), либо функцией, которая возвращает число с плавающей запятой. Работа parser состоит в том, чтобы интерпретировать такие функции (которые на самом деле могут быть несколькими вложенными функциями).   -  person Darrel Hoffman    schedule 08.07.2013
comment
Это часто задаваемый вопрос. Я ответил на него в 2009 году здесь: ericlippert.com/2009/03/03. /представление и личность   -  person Eric Lippert    schedule 08.07.2013
comment
@EricLippert Хорошая статья, довольно хорошо объясняет. Это не делает его менее раздражающим, но, по крайней мере, есть веская причина, почему это не работает так, как вы ожидаете.   -  person Darrel Hoffman    schedule 08.07.2013


Ответы (1)


Когда вы распаковываете тип значения, который был помещен в поле object, вы должны использовать правильное приведение. Преобразование из одного числового типа в другой — это дополнительное приведение, которое происходит после распаковки.

Однако в вашем случае второе приведение действительно неявно, поэтому вы можете немного упростить:

if (result is int)
    return (int)result;
else
    return (float)result;

Почему у вас вообще есть коробочные типы значений? Если вы не знаете, какой тип упакован, для распаковки вам, как правило, придется проверять с помощью is, как вы это делаете.

Обратите внимание, однако, что с типами (IConvertible), подобными этим встроенным числовым типам, вы также можете использовать класс Convert, поэтому:

return Convert.ToSingle(result);

Это выглядит красивее, но я не думаю, что это быстрее. Вероятно, он будет выполнять ту же проверку типов.

Если parser дает только числовые типы, и если по какой-то причине вы настаиваете на упаковке этих типов значений, вы можете также упаковать их в IConvertible, что предполагает изменение возвращаемого типа следующим образом:

private IConvertible parser(string expr) {...}

Тогда ваш код может быть таким:

IConvertible result = parser(expr);  // it's some number boxed in an IConvertible reference type
return result.ToSingle(null);  // instance method in IConvertible
person Jeppe Stig Nielsen    schedule 07.07.2013
comment
Улучшение - по-прежнему требуется временная переменная и проверка типа. Неужели нет никакого способа обойти это? Единственное, что я могу придумать, это уменьшить его до ?: вместо if\ else, но все равно не сохраняет дополнительный шаг. - person Darrel Hoffman; 08.07.2013
comment
Хорошо, трюк Convert.Tosingle в вашем редактировании действительно решает проблему, хотя в настоящее время было бы сложно провести сравнение производительности между ними. Кажется, что обычный кастинг должен быть достаточно умным, чтобы распаковывать и переупаковывать результат по мере необходимости? Во всяком случае, для встроенных типов. - person Darrel Hoffman; 08.07.2013
comment
Что касается вашего последнего редактирования - нет, parser может возвращать довольно много нечисловых типов, поэтому он возвращает object. Он также обрабатывает bools, strings, пользовательский класс Vector4 и Lists всего вышеперечисленного. Некоторые функции скрипта даже возвращают void (хотя внутри они просто возвращают null). Каждый тип имеет свою собственную функцию Parse, такую ​​как ParseFloat выше. Это слишком много кода, чтобы публиковать его здесь, я просто упростил его, чтобы показать проблему. - person Darrel Hoffman; 08.07.2013
comment
@DarrelHoffman Ну, так были созданы правила C #, и, я думаю, на то есть веская причина. Преобразование из int в float представляет собой двоичное изменение представления (одно представляет собой целочисленное представление с дополнением до двух, другое представляет собой представление с плавающей запятой IEEE). Компилятор C# не будет знать, должен ли он выдать как преобразование распаковки, так и числовое преобразование. Единственный другой вариант, который я вижу (кроме идей из моего ответа), - это использовать dynamic. Если r имеет тип dynamic, выражение (float)r будет работать, и до времени выполнения не будет решено, какое преобразование подразумевается. - person Jeppe Stig Nielsen; 08.07.2013
comment
Спасибо - я выбрал return Convert.ToSingle(parser(expr)); Это позволяет избежать создания временной переменной и проверки типа, даже если класс Convert делает то же самое под капотом. Решение dynamic тоже работает, но это дополнительная строка, потому что нужно создать переменную. Не уверен, что лучше с точки зрения производительности, возможно, мне придется протестировать в большем масштабе, чтобы увидеть разницу. - person Darrel Hoffman; 08.07.2013