2012-09-05 5 views
7

Tworzę i kompilowanie wyrażenie z API System.Ling.Expressions. Kompilacja działa dobrze, ale w niektórych przypadkach pojawiają się niewyjaśnione wyjątki NullReferenceException, a nawet wyjątki System.Security.Verification podczas uruchamiania skompilowanej analizy lambda. Dla odniesienia, celem tego projektu jest utworzenie i skompilowanie niestandardowej funkcji serializera dla typu .NET.Weird wyjątki skompilowane dynamicznie zbudowany wyrażenie

Poniżej przedstawiono DebugInfo na wyrażeniu generuje NullReferenceException:

.Lambda #Lambda1<System.Action`2[IO.IWriter,<>f__AnonymousType1`2[System.Int32[],System.Int32]]>(
    IO.IWriter $writer, 
    <>f__AnonymousType1`2[System.Int32[],System.Int32] $t) { 
    .Block() { 
     .Invoke (.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>)(
      $writer, 
      $t.a); 
     .Invoke (.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>)(
      $writer, 
      $t.b) 
    } 
} 

.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>(
    IO.IWriter $writer, 
    System.Int32[] $t) { 
    .Block() { 
     .Invoke (.Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>)(
      $writer, 
      .Call System.Linq.Enumerable.Count((System.Collections.Generic.IEnumerable`1[System.Int32])$t)); 
     .Call IO.SerializerHelpers.WriteCollectionElements(
      (System.Collections.Generic.IEnumerable`1[System.Int32])$t, 
      $writer, 
      .Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>) 
    } 
} 

.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $writer, 
    System.Int32 $t) { 
    .Call $writer.WriteInt($t) 
} 

.Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $w, 
    System.Int32 $count) { 
    .Call $w.BeginWritingCollection($count) 
} 

jest wyjątek w wywołaniu # Lambda3, które nazywa się kilkakrotnie z WriteCollectionElements. Realizacja WriteCollectionElements jest następujący:

static void WriteCollectionElements<T>(IEnumerable<T> collection, IWriter writer, Action<IWriter, T> writeAction) 
     { 
      foreach (var element in collection) 
      { 
       writeAction(writer, element); 
      } 
     } 

Od debugowania wewnątrz tej funkcji I ustalili, że kolekcja, pisarz, writeAction i elementem są niezerowe, gdy jest wyjątek. Argument, że ja przechodząc do skompilowany lambda jest:

new { a = new[] { 20, 10 }, b = 2 } 

też dziwnego, że jeśli usunąć właściwości b i ponownie wygenerować mojej funkcji serializer, wszystko działa bez zarzutu. W tym przypadku DebugInfo dla serializatora jest:

.Lambda #Lambda1<System.Action`2[IO.IWriter,<>f__AnonymousType5`1[System.Int32[]]]>(
    IO.IWriter $writer, 
    <>f__AnonymousType5`1[System.Int32[]] $t) { 
    .Block() { 
     .Invoke (.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>)(
      $writer, 
      $t.a) 
    } 
} 

.Lambda #Lambda2<System.Action`2[IO.IWriter,System.Int32[]]>(
    IO.IWriter $writer, 
    System.Int32[] $t) { 
    .Block() { 
     .Invoke (.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>)(
      $writer, 
      .Call System.Linq.Enumerable.Count((System.Collections.Generic.IEnumerable`1[System.Int32])$t)); 
     .Call IO.SerializerHelpers.WriteCollectionElements(
      (System.Collections.Generic.IEnumerable`1[System.Int32])$t, 
      $writer, 
      .Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>) 
    } 
} 

.Lambda #Lambda3<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $w, 
    System.Int32 $count) { 
    .Call $w.BeginWritingCollection($count) 
} 

.Lambda #Lambda4<System.Action`2[IO.IWriter,System.Int32]>(
    IO.IWriter $writer, 
    System.Int32 $t) { 
    .Call $writer.WriteInt($t) 
} 

Używam .NET Framework 4 (przynajmniej to jest mój cel build) w systemie Windows 7, VS ekspresowe C# 2010.

Czy ktoś ma jakiś pomysł co może być nie tak lub jakie są dalsze kroki, aby spróbować debugować? Z przyjemnością opublikuję więcej informacji, jeśli to pomoże.

EDYCJA: Odkąd (według mojej wiedzy) znalazłem drogę wokół tego błędu, chociaż nie jestem bliżej zrozumienia, dlaczego tak się dzieje. W kodzie, który generuje wyrażeń Mam zamieszczonych powyżej, miałem następujące:

MethodInfo writeCollectionElementsMethod = // the methodInfo for WriteCollectionElements with .MakeGenericMethod() called with typeof(T) 
Expression<Action<IWriter, T> writeActionExpression = // I created this expression separately 
ParameterExpression writerParameter, enumerableTParameter = // parameters of type IWriter and IEnumerable<T>, respectively 

// make an expression to invoke the method 
var methodCallExpression = Expression.Call(
    instance: null, // static 
    method: writeCollectionElementsMethod, 
    arguments: new[] { 
     enumerableTParameter, 
     writerParameter, 
     // passing in this expression correctly would produce the weird error in some cases as described above 
     writeActionExpression 
    } 
); 

// make an expression to invoke the method 
var methodCallExpressionV2 = Expression.Call(
    instance: null, // static 
    method: writeCollectionElementsMethod, 
    arguments: new[] { 
     enumerableTParameter, 
     writerParameter, 
     // this did not cause the bug 
     Expression.Constant(writeActionExpression.Compile()) 
    } 
); 

jednak mi się nie podoba kompilacji każdy wyraz osobno, więc skończyło się na rezygnacji z funkcji WriteCollectionElements całkowicie i po prostu dynamiczne tworzenie pętli foreach za pomocą Expression.Loop, Expression.Break, itp.

W ten sposób nie jestem już zablokowany, ale nadal jestem bardzo ciekawy.

+0

Czy odtwarzalny wyjątek? Czy to zawsze dzieje się dla tego samego "elementu"? –

+0

@ Daniel Hilgarth Tak, wyjątek ma miejsce za każdym razem. Zawsze dzieje się to przy przetwarzaniu tego samego elementu, w tym przypadku 20. – ChaseMedallion

+1

Może można utworzyć małą przykładową aplikację z minimalnym kodem, który odtwarza to zachowanie? –

Odpowiedz

1

Jeśli budować Czynności ręcznie w C# resharper narzeka Lambda1 i Lambda2 niejawnie przechwytywanie zmiennych w clousure

Action<IWriter, int> lambda4 = ((IWriter writer, int length) => writer.BeginWritingCollection(length)); 
Action<IWriter, int> lambda3 = ((IWriter writer, int value) => writer.WriteInt(value)); 
Action<IWriter, int[]> lambda2 = ((IWriter writer, int[] value) => 
    { 
     lambda4(writer, ((IEnumerable<int>) value).Count()); 
     WriteCollectionElements((IEnumerable<int>)value, writer, lambda3); 
    }); 
Action<IWriter, TheData> lambda1 = ((writer, data) => 
    { 
     lambda2(writer, data.a); 
     lambda3(writer, data.b); 
    }); 
class TheData { int[] a; int b; } 

w tym przypadku ReSharper stanach:
„Pośrednio przechwyconego Zamknięcie: lambda2” w wyrażeniu lambda2
„Pośrednio schwytany zamknięcie: lambda4” na ekspresję lambda1

wyjaśnieniem tego jest here i here. Jeśli linia do WriteCollectionElements zostanie usunięta, ostrzeżenie zniknie. Zasadniczo kompilacja JIT tworzy klasę opakowującą dla wewnętrznych wywołań wyrażeń, przechwytując VALUES pisarza i anonimowy typ w celu przekazania akcji BeginWritingCollection do metody statycznej WriteCollectionElements.

Rozwiązaniem byłoby inline sprawozdania z lambda2 do lambda1

Action<IWriter, int> lambda4 = ((IWriter writer, int length) => writer.BeginWritingCollection(length)); 
Action<IWriter, int> lambda3 = ((IWriter writer, int value) => writer.WriteInt(value)); 
Action<IWriter, TheData> lambda1 = ((writer, data) => 
    { 
     lambda4(writer, ((IEnumerable<int>) value.a).Count()); 
     WriteCollectionElements((IEnumerable<int>)value.a, writer, lambda3); 
     lambda3(writer, data.b); 
    }); 
class TheData { int[] a; int b; } 
+0

skompiluj oba zestawy działań i spójrz na bibliotekę DLL w [reflektor | dotPeek | justDecompile | ILSpy], a zobaczysz utworzony typ opakowania. –

+0

Czytam linki i rozumiem ten problem, ale nie widzę, jak to jest związane z moim użyciem Wyrażeń (w przeciwieństwie do nieprzetworzonych akcji/Funkcji) ... – ChaseMedallion

+0

Wyrażenia są przekształcane w równoważne działanie podczas kompilowania wyrażenia. Nie można "wykonać" wyrażenia –