我已经阅读了这个关于如何在(框与否)循环下处理常规的优秀问题。int[]
foreach
Array
确实实现了非泛型IEnumerable
,因此它必须使用object
内部(而不是int
)
但事实证明- 在运行时它实际上被处理为IEnumerable<T>
C#
我如何通过简单的代码测试/证明(没有装箱)它?(而不是通过阅读 IL。)
我喜欢@phoog 的回答,所以只是为了好玩:)
助手类
public static class ILUtils
{
private static Dictionary<short, OpCode> s_opcodes = new Dictionary<short, OpCode>();
static ILUtils()
{
FieldInfo[] opCodeFields = typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
foreach (FieldInfo opCodeField in opCodeFields)
{
if (opCodeField.FieldType != typeof(OpCode))
continue;
OpCode opcode = (OpCode)opCodeField.GetValue(null);
s_opcodes.Add(opcode.Value, opcode);
}
}
public static bool ContainsOpcodes(MethodInfo methodInfo, IEnumerable<OpCode> targetOpCodes)
{
MethodBody methodBody = methodInfo.GetMethodBody();
using (BinaryReader ilReader = new BinaryReader(new MemoryStream(methodBody.GetILAsByteArray())))
{
while (ilReader.BaseStream.Position < ilReader.BaseStream.Length)
{
short opCodeValue = ilReader.ReadByte();
if (opCodeValue == 0xfe)
opCodeValue = (short)(opCodeValue << 8 | ilReader.ReadByte());
OpCode opCode = s_opcodes[opCodeValue];
if (targetOpCodes.Contains(opCode))
return true;
int argumentSize = 4;
if (opCode.OperandType == OperandType.InlineNone)
argumentSize = 0;
else if (opCode.OperandType == OperandType.ShortInlineBrTarget || opCode.OperandType == OperandType.ShortInlineI || opCode.OperandType == OperandType.ShortInlineVar)
argumentSize = 1;
else if (opCode.OperandType == OperandType.InlineVar)
argumentSize = 2;
else if (opCode.OperandType == OperandType.InlineI8 || opCode.OperandType == OperandType.InlineR)
argumentSize = 8;
else if (opCode.OperandType == OperandType.InlineSwitch)
{
int num = ilReader.ReadInt32();
argumentSize = (int)(4 * num + 4);
}
ilReader.BaseStream.Position += argumentSize;
}
}
return false;
}
}
示例用法
private static void BoxingForEach()
{
IEnumerable foo = (IEnumerable)new int[10];
foreach (int i in foo) ;
}
private static void NoBoxingForEach()
{
int[] foo = new int[10];
foreach (int i in foo) ;
}
static void Main(string[] args)
{
MethodInfo boxingForEach = typeof(Program).GetMethod("BoxingForEach", BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo noBoxingForEach = typeof(Program).GetMethod("NoBoxingForEach", BindingFlags.Static | BindingFlags.NonPublic);
Console.WriteLine("BoxingForEach is using boxing: {0}",
ILUtils.ContainsOpcodes(boxingForEach, new[] { OpCodes.Box, OpCodes.Unbox, OpCodes.Unbox_Any }));
Console.WriteLine("NoBoxingForEach is using boxing: {0}",
ILUtils.ContainsOpcodes(noBoxingForEach, new[] { OpCodes.Box, OpCodes.Unbox, OpCodes.Unbox_Any }));
}
结果
BoxingForEach 正在使用拳击:True
NoBoxingForEach 正在使用装箱:False
您的问题错误地假设(与您链接到的问题一样)数组没有实现泛型IEnumerable<T>
. 他们是这样。您可以使用反射看到这一点:
var array = new int[0];
var enumerator = array.GetEnumerator();
var enumeratorType = enumerator.GetType();
var propertyInfo = enumeratorType.GetProperty("Current");
var propertyType = propertyInfo.PropertyType;
Console.WriteLine(propertyType.Name); //prints "Object";
var otherEnumerator = ((IEnumerable<int>)array).GetEnumerator();
enumeratorType = otherEnumerator.GetType();
propertyInfo = enumeratorType.GetProperty("Current");
propertyType = propertyInfo.PropertyType;
Console.WriteLine(propertyType.Name); //prints "Int32";
但是,如果在静态类型数组引用上编写 foreach 循环,C# 编译器会将其转换为 for 循环。我认为没有任何方法可以在不查看 IL 的情况下进行检查。
来自http://msdn.microsoft.com/en-us/library/system.array.aspx:
重要的
从 .NET Framework 2.0 开始,Array 类实现了System.Collections.Generic.IList<T>
、System.Collections.Generic.ICollection<T>
和System.Collections.Generic.IEnumerable<T>
泛型接口。这些实现在运行时提供给数组,因此对文档构建工具不可见。因此,泛型接口不会出现在 Array 类的声明语法中,并且没有接口成员的参考主题,只能通过将数组转换为泛型接口类型(显式接口实现)才能访问这些成员。将数组强制转换为这些接口之一时要注意的关键是添加、插入或删除元素的成员会抛出 NotSupportedException。
如果不查看发出的 IL,就很难验证拳击是否在幕后进行。
但是试试这个:
static void Main()
{
int[] arr1 = { 7, 9, 13, };
Array arr2 = arr1;
IEnumerable arr3 = arr1; // non-generic IEnumerable
foreach (var x in arr1) // hold mouse over var keyword to see compile-time type
{
Overloaded(x); // go to definition to see which overload is used
}
foreach (var x in arr2) // hold mouse over var keyword to see compile-time type
{
Overloaded(x); // go to definition to see which overload is used
}
foreach (var x in arr3) // hold mouse over var keyword to see compile-time type
{
Overloaded(x); // go to definition to see which overload is used
}
}
static void Overloaded(int x)
{
Console.WriteLine("int!");
}
static void Overloaded(object x)
{
Console.WriteLine("object!");
}
很容易看出拳击确实发生在arr2
和arr3
。从技术上讲,我们不能确定是否发生装箱arr1
(必须检查 IL),但我们可以看到隐式类型的 ( var
) 变量变成了一个int
变量,这是一种线索。
这样的事情令人满意吗?
int[] foo = new int[10];
foreach (object o in foo)
{
Console.WriteLine(o.GetType());
int? bar = o as int?;
Console.WriteLine(bar);
}
Console.ReadKey();
如果不检查 IL 的隐式转换,就很难确定。