3

レギュラーが(ボックスかどうかにかかわらず)ループの下でどのように処理されるかについてのこの優れた質問を読みました。int[]foreach

Array非ジェネリックを実装しているので、内部IEnumerableを使用する必要があります(ではなく)objectint

しかし、実行時に実際には次のように処理されたことが判明しましたIEnumerable<T>

C#簡単なコードで(ボクシングがないことを)テスト/証明するにはどうすればよいですか?(ILを読むことではありません。

4

4 に答える 4

5

私は@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

于 2012-12-26T10:17:29.697 に答える
4

あなたの質問は、配列がジェネリックを実装していないことを(リンク先の質問と同様に)誤って想定しています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 をスローすることです。

于 2012-12-26T09:42:02.037 に答える
-1

このようなものは満足ですか?

        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 をチェックせずに確認するのは困難です。

于 2012-12-26T09:22:19.393 に答える