在C#中是否有一种动态隐式类型转换的方法?

12

给定一个具有隐式转换运算符的类:

public class MyDateTime
{
    public static implicit operator MyDateTime(System.Int64 encoded)
    {
        return new MyDateTime(encoded);
    }

    public MyDateTime(System.Int64 encoded)
    {
        _encoded = encoded;
    }
    System.Int64 _encoded;
}

我现在可以做以下事情:

long a = 5;
MyDateTime b = a;

但不包括以下内容:

long f = 5;
object g = f;
MyDateTime h = g;

这会在编译时产生错误:

无法将类型“object”隐式转换为“MyDateTime”。

对我来说很有意义。

现在,我按照以下方式修改了之前的示例:

long f = 5;
object g = f;
MyDateTime h = (MyDateTime)g;

这段代码可以成功编译。但是在运行时,我得到了一个“InvalidCastException”:

无法将类型为“System.Int64”的对象强制转换为“MyDateTime”类型。

这告诉我C#的隐式转换操作符仅在编译时生效,在.NET运行时尝试动态转换对象类型时不会被应用。

我的问题:

  1. 我的理解正确吗?
  2. 是否有其他方法可以实现此功能?

顺便说一下,我的完整应用程序中我正在使用Delegate.DynamicInvoke()来调用需要一个MyDateTime参数的函数,而我向DynamicInvoke传递的参数类型是一个长整型。

3个回答

14

我是正确的吗?

是的,你是正确的。严谨一点说,你应该使用“用户定义的隐式转换”而不是“隐式转换”--转换(几乎)总是显式的。但是你的推断,重载解析选择在编译时而不是运行时调用哪个用户定义的转换操作是正确的。

还有其他方法吗?

是的,在C# 4中如果将“object”类型声明为“dynamic”则会在运行时重新启动编译器,并重新对操作数进行分析,就好像它们的编译时类型是当前的运行时类型一样。正如你所想象的那样,这并不便宜,尽管我们非常聪明地缓存和重复使用结果,以防你在紧密循环中这样做。


只是出于好奇,为什么运算符在运行时不像方法那样工作呢?根据您的答案,它们似乎只是语法糖而已。在我看来,如果这些运算符被提升为任何类型的一级成员(这意味着它们成为所有其他面向对象的好处,如继承、重写和接口的一部分),那么C#将变得更加强大。默认情况下在运行时利用它们是否意味着对语言的设计和实现进行重大更改? - MarioDS
1
@MDeSchaepmeester:你说得没错,这里确实有点不一致。例如,比较int和decimal的设计。你可以说Func<decimal, decimal> add = decimal.Add;,但是对于int来说却没有这样的方法;你必须说Func<int, int> add = (x, y) => x + y。如果所有内置类型都像Decimal一样设计,然后运行时或编译器可以选择将它们降级为更基本的操作以提高性能,那就太好了。但这种一致性真正源于“函数式”思维方式。 - Eric Lippert
1
@MDeSchaepmeester:我怀疑运行时的原始设计者在设计类型系统和内置类型的操作时根本没有考虑到这种统一的函数抽象。当然,十进制数既有operator+又有Add方法也很奇怪。更不用说表示相等操作的十几种方式了!实际上有点混乱。下次从头开始设计框架时,请尽早正确处理这些问题。 - Eric Lippert
1
@MDeSchaepmeester:正如您所指出的,这些东西都不适用于接口。一个经常被提出的功能是添加“静态接口”,使您可以说Matrix<T> where T : IAddable<T, T>等等。 这确实需要对语言和运行时进行一些相当严格的工作,因此它从未成为优先级列表中非常重要的事项。 - Eric Lippert
@EricLippert:Jon Skeet的MiscUtil以相对简洁的方式提供了通用运算符(例如Operator.Add<T>Operator.Add<TArg1,TArg2>)。这可以为您提供通用运算符函数,但不会给您带来更重要的类型安全。但是,您可能可以通过Code Contracts获得类似的保护。 - Brian
我能否在表达式Lambda中以某种方式使用动态隐式转换?我有类似于Expression.Lambda(delegateType, Expression.Convert(getValExpr, resType), exprParameters)的东西,但是processCallExpr结果和object不同。例如:processCall的类型为int,resType类型为double,我需要一些表达式来隐式转换它,而不是强制转换错误。 - mvorisek

-3

我知道这是一个比较旧的问题,但如果有其他人遇到了同样的问题,这段代码可以编译并运行:

long f = 5;
object g = f;
MyDateTime h = g as MyDateTime;

它从技术上来说会运行良好,也就是不会抛出“InvalidCastException”异常,但是“h”将为“null”,这不是OP想要或期望的。 - MarioDS

-4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接