I've read this excellent question about how a regular int[]
is handled under foreach
( box or not) loop.
Array
does implement the non-generic IEnumerable
so it has to use anobject
inside ( and not int
)
But it turns out that - in run-time it actually handled as IEnumerable<T>
How can I test/proof (that there isn't boxing )it by a simple C#
code? ( and not by reading IL.)
I like @phoog's answer, so just for fun :)
Helper class
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;
}
}
Example usage
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 }));
}
Results
BoxingForEach is using boxing: True
NoBoxingForEach is using boxing: False
Your question assumes incorrectly (as does the question you link to) that arrays do not implement the generic IEnumerable<T>
. They do. You can see this using reflection:
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";
However, if you write a foreach loop on a statically-typed array reference, the C# compiler translates it to a for loop. I don't think there's any way to check that without looking at the IL.
From http://msdn.microsoft.com/en-us/library/system.array.aspx:
Important
Starting with the .NET Framework 2.0, the Array class implements the System.Collections.Generic.IList<T>
, System.Collections.Generic.ICollection<T>
, and System.Collections.Generic.IEnumerable<T>
generic interfaces. The implementations are provided to arrays at run time, and therefore are not visible to the documentation build tools. As a result, the generic interfaces do not appear in the declaration syntax for the Array class, and there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With