为什么将枚举数组强制转换为两个不同的IEnumerable< T>类型? [英] Why can enum arrays be cast to two different IEnumerable<T> types?

查看:69
本文介绍了为什么将枚举数组强制转换为两个不同的IEnumerable< T>类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我似乎偶然发现了我不完全理解的C#中的一些异常情况。假设我定义了以下枚举:

 公共枚举Foo:short 
{
//值不是很重要
A,
B
}

如果我声明一个 Foo 数组或通过 Enum.GetValues 检索一个数组,则可以成功地将其转换为 IEnumerable< short> IEnumerable< ushort> 都可以。例如:

  Foo [] values = Enum.GetValues(typeof(Foo)); 
//此强制转换成功完成。
var asShorts =(IEnumerable< short>)值;
//此转换也成功,这是出乎意料的。
var asUShorts =(IEnumerable< ushort>)值;
//强制转换失败:
var asInts =(IEnumerable< int>)值;

其他基础类型也会发生这种情况。枚举数组似乎总是可以转换为基础整数类型的有符号和无符号版本。



我不知所措,无法解释这种行为。

解决方案

这不仅是 IEnumerable< T> -您也可以将其强制转换为 array 类型,只要您先使编译器傻瓜:

 公共枚举Foo:短
{
A,B
}

类测试
{
static void Main()
{
Foo [] foo = new Foo [10];
short [] shorts =(short [])(object)foo;
ushort [] ushorts =(ushort [])(object)foo;
}
}

C#语言无法期望这种转​​换是可行的,但CLR很乐意这样做。反之亦然-您可以将 short [] 转换为 Foo [] 。哦,如果您有另一个具有相同基础类型的枚举,也可以强制转换为该枚举。基本上,CLR知道所有这些类型都是16位整数-所有位模式都是有效值,即使它们具有不同的含义-也可以让您将一种类型视为另一种。我不认为CLI规范中记录了该问题-无论如何我都找不到对此的引用。



这可能会导致某些问题仅出于记录目的,将LINQ中的优化(在 Cast<> ToList 中)组合在一起时会产生一些有趣的问题。 / p>

例如:

  int [] foo = new int [10 ]; 
var list = foo.Cast< uint>()。ToList();

您可能希望它带有 InvalidCastException (例如,如果您从带有任何值的 List< int> 开头,则这样做),但最终却得到:

 未处理的异常:System.ArrayTypeMismatchException:无法将源数组类型分配给目标数组类型。 
在System.Array.Copy(数组sourceArray,Int32 sourceIndex,数组destinationArray,Int32 destinationIndex,Int32长度,布尔值可靠)
在System.SZArrayHelper.CopyTo [T](T []数组,Int32索引)System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
在System.Linq.Enumerable.ToList [TSource](IEnumerable`1 source)
在Test.Main()

这是因为 Cast 操作已检查并发现 int [] 已经是 uint [] ,因此它已正确传递到 ToList(),然后尝试使用 Array.Copy 这样做区别。哎呀!


I seemed to have stumbled upon something unusual within C# that I don't fully understand. Say I have the following enum defined:

public enum Foo : short 
{
    // The values aren't really important
    A,
    B
}

If I declare an array of Foo or retrieve one through Enum.GetValues, I'm able to successfully cast it to both IEnumerable<short> and IEnumerable<ushort>. For example:

Foo[] values = Enum.GetValues( typeof( Foo ) );
// This cast succeeds as expected.
var asShorts = (IEnumerable<short>) values;
// This cast also succeeds, which wasn't expected.
var asUShorts = (IEnumerable<ushort>) values;
// This cast fails as expected:
var asInts = (IEnumerable<int>) values;

This happens for other underlying types as well. Arrays of enums always seem to be castable to both the signed and unsigned versions of the underlying integral type.

I'm at a loss for explaining this behavior. Is it well-defined behavior or have I just stumbled onto a peculiar quirk of the language or CLR?

解决方案

It's not just IEnumerable<T> - you can cast it to the array type as well, so long as you fool the compiler first:

public enum Foo : short 
{
    A, B
}

class Test
{
    static void Main()
    {
        Foo[] foo = new Foo[10];
        short[] shorts = (short[]) (object) foo;
        ushort[] ushorts = (ushort[]) (object) foo;        
    }
}

The C# language provides no expectation of this conversion being feasible, but the CLR is happy to do it. The reverse is true too - you could cast from a short[] to a Foo[]. Oh, and if you had another enum with the same underlying type, you could cast to that, too. Basically, the CLR knows that all of these types are just 16-bit integers - all bit patterns are valid values, even though they'll have different meanings - so it lets you treat one type as another. I don't think that's documented in the CLI specification - I couldn't find any reference to it, anyway.

This can cause some interesting problems when optimizations in LINQ (in Cast<> and ToList) are combined, just for the record.

For example:

int[] foo = new int[10];
var list = foo.Cast<uint>().ToList();

You might expect this to come up with an InvalidCastException (as it does if you start with a List<int> with any values, for example) but instead you end up with:

Unhandled Exception: System.ArrayTypeMismatchException: Source array type cannot be assigned to destination array type.
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
   at System.SZArrayHelper.CopyTo[T](T[] array, Int32 index)
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Test.Main()

That's because the Cast operation has checked and found that the int[] is already a uint[], so it's passed it right along to ToList(), which then tries to use Array.Copy, which does notice the difference. Ouch!

这篇关于为什么将枚举数组强制转换为两个不同的IEnumerable&lt; T&gt;类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆