泛型:铸造和价值类型,为什么这是非法的?

Mat*_*eer 12 .net c# generics casting .net-4.0

为什么这是编译时错误?

public TCastTo CastMe<TSource, TCastTo>(TSource i)
{
     return (TCastTo)i;
}
Run Code Online (Sandbox Code Playgroud)

错误:

annot将类型'TSource'转换为'TCastTo'

为什么这是一个运行时错误?

public TCastTo CastMe<TSource, TCastTo>(TSource i)
{
     return (TCastTo)(object)i;
}

int a = 4;
long b = CastMe<int, long>(a); // InvalidCastException

// this contrived example works
int aa = 4;
int bb = CastMe<int, int>(aa);

// this also works, the problem is limited to value types
string s = "foo";
object o = CastMe<string, object>(s);
Run Code Online (Sandbox Code Playgroud)

我搜索了SO和互联网以获得答案,并找到了类似的通用相关铸造问题的许多解释,但我找不到任何关于这个特殊的简单案例.

Eri*_*ert 24

为什么这是编译时错误?

问题是每种可能的值类型组合对于强制转换的含义都有不同的规则.将64位双精度转换为16位int与将十进制转换为浮点数完全不同,依此类推.可能性的数量是巨大的.所以想像编译器一样.编译器应该为您的程序生成什么代码?

编译器必须生成在运行时再次启动编译器的代码,对类型进行全新分析,并动态发出适当的代码.

这看起来似乎可能比你预期的泛型更多的工作和更少的性能,所以我们只是取缔它.如果您真正想要的是编译器再次启动并对类型进行分析,请在C#4中使用"dynamic"; 这就是它的作用.

为什么这是一个运行时错误?

同样的道理.

由于与上述相同的原因,盒装的int只能解包为int(或int?); 如果CLR尝试从盒装值类型到每个其他可能的值类型进行所有可能的转换,那么基本上它必须在运行时再次运行编译器.这将出乎意料地缓慢.

那么为什么它不是参考类型的错误?

因为每个引用类型转换与每个其他引用类型转换相同:您询问对象以查看它是从所需类型派生还是与所需类型相同.如果不是,则抛出异常(如果进行强制转换)或结果为null/false(如果使用"as/is"运算符).规则对于引用类型是一致的,它们不是值类型.记住引用类型知道自己的类型.价值类型没有; 对于值类型,执行存储的变量是唯一知道适用于这些位的类型语义的东西.值类型包含其值,不包含其他信息.引用类型包含其值以及大量额外数据.

有关更多信息,请参阅我关于此主题的文章:

http://ericlippert.com/2009/03/03/representation-and-identity/

  • @Ben:当然,也许吧.有关系吗?询问哪种表达式树编译"更有效",就像要问哪个1970年代的凯迪拉克更加省油.如果您关心燃油效率,请不要首先驾驶1970年代的凯迪.涉及表达式树的任何解决方案都比执行静态确定的转换更昂贵.表达树是重量级的. (4认同)
  • @Ben:这正是动态的作用; DLR执行类型分析,构造表达式树,编译它并缓存结果.下次遇到代码时,DLR会查询其缓存,确定类型是否足够匹配以重新使用缓存状态,并在可能的情况下重用它. (2认同)

Ben*_*igt 7

C#对多个不同的底层操作使用一种强制转换语法:

  • 向上转型
  • 沮丧
  • 拳击
  • 拆箱
  • 数字转换
  • 用户定义的转换

在通用上下文中,编译器无法知道哪些是正确的,并且它们都生成不同的MSIL,因此它会挽救.

通过编写return (TCastTo)(object)i;,强制编译器执行向上转换object,然后向下转换为TCastTo.编译器将生成代码,但如果这不是转换相关类型的正确方法,则会出现运行时错误.


代码示例:

public static class DefaultConverter<TInput, TOutput>
{
    private static Converter<TInput, TOutput> cached;

    static DefaultConverter()
    {
        ParameterExpression p = Expression.Parameter(typeof(TSource));
        cached = Expression.Lambda<Converter<TSource, TCastTo>(Expression.Convert(p, typeof(TCastTo), p).Compile();
    }

    public static Converter<TInput, TOutput> Instance { return cached; }
}

public static class DefaultConverter<TOutput>
{
     public static TOutput ConvertBen<TInput>(TInput from) { return DefaultConverter<TInput, TOutput>.Instance.Invoke(from); }
     public static TOutput ConvertEric(dynamic from) { return from; }
}
Run Code Online (Sandbox Code Playgroud)

埃里克的方式肯定会更短,但我认为我应该更快.

  • @Matt :(无约束)泛型阻止编译器尝试找出你需要的转换.但是,无论是否涉及泛型,进行错误的样式转换都会导致失败. (2认同)