Функция 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
перед их распаковкой через ту или иную функцию).