【问题标题】:A more elegant approach to a generic type conversion function一种更优雅的泛型类型转换函数方法
【发布时间】:2020-04-02 11:35:52
【问题描述】:

泛型函数应采用装箱 (object) 值并将其转换为指定类型(在合理范围内)。
以下内容有效,但看起来很糟糕(代码和生成的 IL)并最终无用地装箱拆箱(因为编译器不同意将原语隐式转换为泛型类型):

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# 对拆箱时的类型不匹配非常宽容(例如,尝试将浮点数拆箱为双精度会引发异常)。

所以我决定做一些像这样的通用包装器:

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 形式的参数,然后才能通过一个或其他函数将它们拆箱)。

【问题讨论】:

  • 你试过用Convert.ChangeType吗?
  • 附带说明,如果val 始终是盒装值类型,请将T 约束为值类型并删除object 大小写,这是荒谬的;如果您已经有了object,为什么还要打电话给convert&lt;object&gt;
  • object-case 的唯一目的是允许在后面显示的通用包装器中使用非特定的参数类型,尽管提到让我意识到我可以很好地在最后的 else-块(好像转换失败,和下面的throw一样)。幸运的是,因为我现在知道 Convert.ChangeType 存在,所以这段代码将继续使用。

标签: c# type-conversion


【解决方案1】:

您可以使用利用IConvertible 接口并完全避免强制转换的静态类System.Convert 来回避与取消装箱到对象的确切类型相关的所有问题。

正如您注意到的那样,这将在运行时失败:

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 方法的代码;您不必遍历可以为每种目标类型装箱的所有可能类型。

好消息是,如果您将 Pavel 在上面的评论中指出的通用 Convert.ChangeType 方法添加到组合中,您仍然可以大大改善这一点。如果可能的话,这种方法已经具备了弄清楚如何进行转换的所有方法(您可能正在尝试重新发明轮子):

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

请务必注意,如果您使用 Convert 而不是您的代码,可能会存在一些语义差异。当转换为整数类型时,您的将截断十进制值,Convert 将四舍五入。如果这可能是一个问题,那么在某些情况下您可能必须坚持使用您的代码。

【讨论】:

  • 谢谢! Convert.ChangeType 正是我要找的东西。舍入行为是可以接受的,我认为这减少了禁止一些异常转换(如 string->int)的工作。
猜你喜欢
  • 2012-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-14
  • 1970-01-01
  • 2011-08-09
  • 2021-02-20
  • 1970-01-01
相关资源
最近更新 更多