C# 在常规数组上测试foreach box/unbox?

C# 在常规数组上测试foreach box/unbox?,c#,.net,arrays,foreach,boxing,C#,.net,Arrays,Foreach,Boxing,我读过关于如何在foreach(框或非框)循环下处理常规int[] Array实现了非泛型的IEnumerable,因此它必须在内部使用对象(而不是int) 但是,在运行时,它实际上是作为IEnumerable 我如何通过一个简单的C代码来测试/证明(没有装箱)它?(而不是通过阅读IL。)像这样的事情令人满意吗 int[] foo = new int[10]; foreach (object o in foo) { Con

我读过关于如何在
foreach
(框或非框)循环下处理常规
int[]

Array
实现了非泛型的
IEnumerable
,因此它必须在内部使用
对象(而不是
int

但是,在运行时,它实际上是作为
IEnumerable


我如何通过一个简单的
C
代码来测试/证明(没有装箱)它?(而不是通过阅读IL。)

像这样的事情令人满意吗

        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是否存在隐式强制转换,就很难确定。

您的问题错误地假设数组没有实现泛型
IEnumerable
。是的。您可以使用反射看到这一点:

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";
var数组=新整数[0];
var enumerator=array.GetEnumerator();
var enumeratorType=enumerator.GetType();
var propertyInfo=enumeratorType.GetProperty(“当前”);
var propertyType=propertyInfo.propertyType;
Console.WriteLine(propertyType.Name)//打印“对象”;
var otherEnumerator=((IEnumerable)数组).GetEnumerator();
enumeratorType=otherEnumerator.GetType();
propertyInfo=enumeratorType.GetProperty(“当前”);
propertyType=propertyInfo.propertyType;
Console.WriteLine(propertyType.Name)//打印“Int32”;
但是,如果在静态类型的数组引用上编写foreach循环,C#编译器会将其转换为for循环。我不认为有任何方法可以在不看IL的情况下检查它

发件人:

重要的


从.NET Framework 2.0开始,Array类实现了
System.Collections.Generic.IList
System.Collections.Generic.ICollection
System.Collections.Generic.IEnumerable
通用接口。这些实现在运行时提供给阵列,因此对于文档构建工具不可见。因此,泛型接口不会出现在数组类的声明语法中,并且没有接口成员的引用主题,这些主题只能通过将数组强制转换为泛型接口类型(显式接口实现)来访问。当您将数组强制转换到这些接口之一时,需要注意的关键是添加、插入或删除元素的成员抛出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
变量,这是一种线索。

我喜欢@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;
    }
}
结果

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;
    }
}
BoxingForEach正在使用装箱:True

NoBoxingForEach正在使用装箱:False


实际上,C#编译器将数组的
foreach
转换为
for
。你为什么说“而不是通过读取IL”?查看编译器实际输出的内容似乎是这里唯一明智的选择…@JonSkeet,如果这是唯一的解决方案…那么好吧。如果您阅读其中的“foreach
语句”,您将看到数组被作为一种特殊情况处理。Quote:如果表达式的类型
X
是数组类型[…],则元素类型是数组类型
X
的元素类型。[结束引用]注意,如果它是多维数组,如
int[,]
int[,,]
,您仍然可以
foreach
通过它而无需装箱。Jon Skeet在前几天(查看IL)验证了这一点(请参阅对的注释)。@RoyiNamir GetType返回
typeof(int)
的事实并不能证明没有装箱。@RoyiNamir看到了我刚才添加到答案中的MSDN的引用。ohhhh sh****t!的确很重要。谢谢你的信息!p、 他们是怎么做到的?在运行时应用
Ienumerable…
。。。。?这让我想知道,整个运行时应用程序是否会影响性能…@RoyiNamir也许它确实会影响性能,但我怀疑它是否有重大影响。我设想实现是针对每个数组类型添加一次,而不是针对每个数组实例添加一次。他们在运行时是如何做到的?(声明:“从现在起,您将实现Ienumerable…”@RoyiNamir,因为每种类型都会加载到内存中一次(我相信每个AppDomain)。类型成员由类型的内存中表示形式定义,该表示形式在加载后不会更改。每个对象的内存中表示法都包含一个指向其类型的内存中表示法的指针。这当然会有装箱,因为您将
int
放入编译时类型
对象的变量
o
@JeppeStigNielsen更不用说调用
GetType()
会导致运行时对int进行装箱,因此即使它是
foreach(foo中的int i)
,您仍然会进行装箱。我不知道GetType()会导致运行时装箱。但是想不出更好的方法了。我很高兴看到一些真正相关的事情。@RoyiNamir,不,我不是那个意思。试试这个:
Console.WriteLine(1.GetType())。这编译成IL,意思是:加载1;盒子;调用object.GetType。您只能对对象的引用调用GetType,因此如果您想对值类型的实例调用GetType,编译器必须首先将其装箱。我很高兴我弄错了(并被更正了),比如,Lukior,我也不知道。这是我很久以来看到的最幽默的答案之一。没有“阅读IL”的证明!