«CLR обнаружил неверную программу» при использовании Enumerable.ToDictionary с методом расширения - PullRequest
14 голосов
/ 15 февраля 2012

Коллега передал мне интересный пример кода, который вылетает с InvalidProgramException («CLR обнаружил недопустимую программу») при запуске.

Проблема, кажется, возникает во время JIT, в том смысле, что это прекрасно компилируется, но выдает исключение непосредственно перед вызовом метода с «ошибочной» строкой - я думаю, что это JIT'd.

В рассматриваемой строке вызывается Enumerable.ToDictionary и передается Func в качестве второго аргумента.

Если аргумент Func полностью указан с лямбдой, он работает; если он указан как группа методов, если не удается. Конечно, эти два эквивалентны?

Это поставило меня в тупик (и коллегу, который обнаружил это!) - и это, безусловно, похоже на ошибку JIT.

[РЕДАКТИРОВАТЬ: Извините - я получил пропуски и неудачи в неправильном обходе в примере кода - теперь исправлено (описание выше было правильным)]

У кого-нибудь есть объяснение?

using System;
using System.Linq;

internal class Program
{
    private static void Main(string[] args)
    {
        Test.Try();
    }
}

public class Test
{
    public static readonly int[] integers = new[] { 1, 3, 5 };
    public static void Try()
    {
        var line = new Line { A = 3, B = 5 };

        // PASSES
        var dict = integers.ToDictionary<int, int, decimal>(i => i, i => line.Compute(i));

        // FAILS
        //var dict = integers.ToDictionary<int, int, decimal>(i => i, line.Compute);

        Console.WriteLine(string.Join(" ", dict.Select(kv => kv.Key + "-" + kv.Value)));
    }
}

public class Line
{
    public decimal A;
    public decimal B;
}

public static class SimpleCompute
{
    public static decimal Compute(this Line line, int value)
    {
        return line.A*value + line.B;
    }
}

1 Ответ

15 голосов
/ 15 февраля 2012

Ошибка компилятора.

Для информации, у меня есть асинхронная CTP, которая может быть связана; csc отчеты: 4.0.30319.440

Кажется, разница между:

public static void TryTwo() // fails
{
    var line = new Line {A = 3, B = 5};

    var dict = integers.ToDictionary<int, int, decimal>(i => i, line.Compute);
    Console.WriteLine("TryTwo complete");
}
public static void TryFive() // works
{
    var line = new Line { A = 3, B = 5 };

    Func<int, decimal> func = line.Compute;
    var dict = integers.ToDictionary<int, int, decimal>(i => i, func);
    Console.WriteLine("TryFour complete");
}

так что давайте посмотрим на отражатель:

.method public hidebysig static void TryTwo() cil managed
{
    .maxstack 4
    .locals init (
        [0] class Line <>g__initLocal6)
    L_0000: newobj instance void Line::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: ldc.i4.3 
    L_0008: newobj instance void [mscorlib]System.Decimal::.ctor(int32)
    L_000d: stfld valuetype [mscorlib]System.Decimal Line::A
    L_0012: ldloc.0 
    L_0013: ldc.i4.5 
    L_0014: newobj instance void [mscorlib]System.Decimal::.ctor(int32)
    L_0019: stfld valuetype [mscorlib]System.Decimal Line::B
    L_001e: ldsfld int32[] Test::integers
    L_0023: ldsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegate8
    L_0028: brtrue.s L_003b
    L_002a: ldnull 
    L_002b: ldftn int32 Test::<TryTwo>b__7(int32)
    L_0031: newobj instance void [mscorlib]System.Func`2<int32, int32>::.ctor(object, native int)
    L_0036: stsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegate8
    L_003b: ldsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegate8
    L_0040: call class [mscorlib]System.Collections.Generic.Dictionary`2<!!1, !!2> [System.Core]System.Linq.Enumerable::ToDictionary<int32, int32, valuetype [mscorlib]System.Decimal>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>, class [mscorlib]System.Func`2<!!0, !!2>)
    L_0045: pop 
    L_0046: ldstr "TryTwo complete"
    L_004b: call void [mscorlib]System.Console::WriteLine(string)
    L_0050: ret 
}

против

 .method public hidebysig static void TryFive() cil managed
{
    .maxstack 4
    .locals init (
        [0] class Line line,
        [1] class [mscorlib]System.Func`2<int32, valuetype [mscorlib]System.Decimal> func,
        [2] class Line <>g__initLocal9)
    L_0000: newobj instance void Line::.ctor()
    L_0005: stloc.2 
    L_0006: ldloc.2 
    L_0007: ldc.i4.3 
    L_0008: newobj instance void [mscorlib]System.Decimal::.ctor(int32)
    L_000d: stfld valuetype [mscorlib]System.Decimal Line::A
    L_0012: ldloc.2 
    L_0013: ldc.i4.5 
    L_0014: newobj instance void [mscorlib]System.Decimal::.ctor(int32)
    L_0019: stfld valuetype [mscorlib]System.Decimal Line::B
    L_001e: ldloc.2 
    L_001f: stloc.0 
    L_0020: ldloc.0 
    L_0021: ldftn valuetype [mscorlib]System.Decimal SimpleCompute::Compute(class Line, int32)
    L_0027: newobj instance void [mscorlib]System.Func`2<int32, valuetype [mscorlib]System.Decimal>::.ctor(object, native int)
    L_002c: stloc.1 
    L_002d: ldsfld int32[] Test::integers
    L_0032: ldsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegateb
    L_0037: brtrue.s L_004a
    L_0039: ldnull 
    L_003a: ldftn int32 Test::<TryFive>b__a(int32)
    L_0040: newobj instance void [mscorlib]System.Func`2<int32, int32>::.ctor(object, native int)
    L_0045: stsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegateb
    L_004a: ldsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegateb
    L_004f: ldloc.1 
    L_0050: call class [mscorlib]System.Collections.Generic.Dictionary`2<!!1, !!2> [System.Core]System.Linq.Enumerable::ToDictionary<int32, int32, valuetype [mscorlib]System.Decimal>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>, class [mscorlib]System.Func`2<!!0, !!2>)
    L_0055: pop 
    L_0056: ldstr "TryFour complete"
    L_005b: call void [mscorlib]System.Console::WriteLine(string)
    L_0060: ret 
}

Если вы посмотрите в ломаной версии, загружает только один делегат . Ошибка компилятора, в основном:

L_0023: ldsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegate8
L_0028: brtrue.s L_003b
L_002a: ldnull 
L_002b: ldftn int32 Test::<TryTwo>b__7(int32)
L_0031: newobj instance void [mscorlib]System.Func`2<int32, int32>::.ctor(object, native int)
L_0036: stsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegate8
L_003b: ldsfld class [mscorlib]System.Func`2<int32, int32> Test::CS$<>9__CachedAnonymousMethodDelegate8
L_0040: call class [mscorlib]System.Collections.Generic.Dictionary`2<!!1, !!2> [System.Core]System.Linq.Enumerable::ToDictionary<int32, int32, valuetype [mscorlib]System.Decimal>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, !!1>, class [mscorlib]System.Func`2<!!0, !!2>)

все вышеперечисленное «проверяет, существует ли кэшированный i => i; если нет, создайте его; затем загрузите его». Он никогда ничего не делает со вторым делегатом. Следовательно, в стеке недостаточно значений для вызова метода.

...