Более элегантный подход к обобщенной функции преобразования типов c - PullRequest
0 голосов
/ 02 апреля 2020

Функция generi c должна принимать в штучной упаковке (object) значение и приводить его к указанному типу (в разумных пределах).
Следующее работает, но выглядит плохо (как код, так и полученный IL) и заканчивает бокс-распаковку даром (поскольку компилятор не соглашается неявно приводить примитив к типу generi c):

static T convert<T>(object val) {
    var t = typeof(T);
    if (t == typeof(int)) {
        if (val is int) return (T)val;
        if (val is double) return (T)(object)(int)(double)val;
        if (val is float) return (T)(object)(int)(float)val;
        if (val is bool) return (T)(object)((bool)val ? 1 : 0);
    } else if (t == typeof(double)) {
        if (val is double) return (T)val;
        if (val is float) return (T)(object)(double)(float)val;
        if (val is int) return (T)(object)(double)(int)val;
        if (val is bool) return (T)(object)((bool)val ? 1.0 : 0.0);
    } else if (t == typeof(float)) {
        if (val is float) return (T)val;
        if (val is double) return (T)(object)(float)(double)val;
        if (val is int) return (T)(object)(float)(int)val;
        if (val is bool) return (T)(object)((bool)val ? 1f : 0f);
    } else if (t == typeof(bool)) {
        if (val is bool) return (T)val;
        if (val is double) return (T)(object)((double)val != 0.0);
        if (val is float) return (T)(object)((float)val != 0f);
        if (val is int) return (T)(object)((int)val != 0);
    } else return (T)val;
    throw new Exception($"Can't convert ${val} to ${typeof(T)}");
}

Контекст: внешняя библиотека реализует обобщенные обертки функций / замыканий через класс, подобный это

class Function {
    public Function(int argCout) { ... }
    public object invoke_0() { ... }
    public object invoke_1(object v0) { ... }
    public object invoke_2(object v0, object v1) { ... }
    // and so on...
}

, от которого вы наследуете, вот так:

class MyFunc : Function {
    public MyFunc() : base(1) {}
    public override object invoke_1(object v0) {
        // (... cast v0 to appropriate type and call the destination function)
    }
}

Будьте уверены, у меня не займет много времени понять, что:

  • Хотя это довольно эффективно, трудно организовать несколько крошечных классов, поскольку они будут находиться на некотором расстоянии от их использования в коде (не может объявить середину выражения класса).
  • C# довольно непростительно о несоответствиях типов при распаковке (например, попытка распаковать float как double приведет к исключению).

Поэтому я решил сделать несколько обобщенных c оболочек, таких как:

class WrapAction<T> : Function {
    Action<T> action;
    public MyFunc(Action<T> act) : base(1) {
        action = act;
    }
    public override object invoke_1(object v0) {
        action.Invoke(WrapHelpers.convert<T>(v0));
        return null;
    }
}

(где WrapHelpers.convert - функция, показанная в начале вопроса).

Поскольку это позволяет вместо этого делать

Function fun = new WrapAction<string>((str) => Console.WriteLine(str));

.

Другие примечания:

  • Переключение object на dynamic позволяет скрыть некоторые операции распаковки, но в остальном бесполезно.
  • Создание различных convert версий / перегрузок для разных целей Типы позволяет исключить некоторые проверки за счет добавленного количества строк (поскольку ваш делегат теперь должен принимать аргументы как objects перед их распаковкой через ту или иную функцию).

1 Ответ

2 голосов
/ 02 апреля 2020

Вы можете обойти все проблемы, связанные с распаковкой, с точным типом объекта, используя stati c class System.Convert, использующий интерфейс IConvertible и избегающий приведений.

Это, как Вы хорошо заметите, что произойдет сбой во время выполнения:

object o = (short)1;
var i = (iny)o; //runtime exception

Но это не будет:

object o = (short)1;
var i = Convert.ToInt32(o); //calls IConvertible.ToInt32 explicitly implemented in System.Int16

Одно это может немного улучшить код вашего convert метода; вам не нужно go просматривать все возможные типы, которые могут быть помещены в коробку для каждого типа цели.

Хорошей новостью является то, что вы все еще можете значительно улучшить это, если добавите к миксу общий метод Convert.ChangeType, на который Павел указывает в своем комментарии выше. В этом методе уже есть все необходимое, чтобы выяснить, как выполнить преобразование, если это возможно (возможно, вы пытаетесь заново изобрести колесо здесь):

static T convert<T>(object val)  =>
    (T)Convert.ChangeType(o, typeof(T))

Важно отметить, что может быть некоторая семантика c различия, если вы используете Convert вместо вашего кода. Ваши будут обрезать десятичные значения при преобразовании в целое число типа c, округляется Convert. Если это может быть проблемой, то вам, возможно, придется придерживаться своего кода в некоторых сценариях ios.

...