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

时间:2011-04-07 21:49:36

标签: c# .net generics .net-4.0 casting

为什么这是编译时错误?

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

错误:

  

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);

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

3 个答案:

答案 0 :(得分:23)

  

为什么这是编译时错误?

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

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

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

  

为什么这是运行时错误?

同样的道理。

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

  

那么为什么引用类型不是错误呢?

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

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

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

答案 1 :(得分: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; }
}

Eric的方式肯定会更短,但我认为我应该更快。

答案 2 :(得分:2)

导致编译错误,因为无法将TSource隐式强制转换为TCastTo。这两种类型可以在其继承树上共享一个分支,但不能保证。如果您只想调用共享祖先的类型,则应修改CastMe()签名以使用祖先类型而不是泛型。

运行时错误示例通过首先将TSource i强制转换为对象来避免第一个示例中的错误,这是C#中所有对象派生自的对象。虽然编译器没有抱怨(因为对象 - >从它派生的东西,可能是有效的),如果强制转换无效,则抛出via(Type)变量语法的行为将抛出。 (与编译器在示例1中阻止发生的问题相同)。

另一种解决方案,它与你正在寻找的东西类似...

    public static T2 CastTo<T, T2>(T input, Func<T, T2> convert)
    {
        return convert(input);
    }

你会这样称呼它。

int a = 314;
long b = CastTo(a, i=>(long)i);

希望这有帮助。