2012-09-05 14 views
7

Sto creando e compilando un'espressione con l'API System.Ling.Expressions. La compilazione funziona bene, ma in alcuni casi ottengo inspiegabili NullReferenceExceptions o anche System.Security.Verification eccezioni durante l'esecuzione del lambda compilato. Per riferimento, lo scopo di questo progetto è creare e compilare una funzione serializzatore personalizzata per un tipo .NET.Strane eccezioni compilate espressione dinamicamente creata

che segue è la DebugInfo per un'espressione che getta una 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) 
} 

L'eccezione viene generata all'interno della chiamata a # Lambda3, che è chiamato più volte da WriteCollectionElements. L'implementazione di WriteCollectionElements è come segue:

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

Da debugging all'interno di questa funzione, ho determinato che raccolta, scrittore, writeAction, e sono tutti elementi non nulli quando viene generata l'eccezione. L'argomento che sto passando al lambda compilato è:

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

anche strano è che se tolgo la proprietà b e ri-generare la mia funzione serializzatore, tutto funziona bene. In questo caso il DebugInfo per il serializzatore è:

.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) 
} 

Sono in esecuzione .NET Framework 4 (almeno questo è il mio obiettivo build) su Windows 7, VS espresso C# 2010.

Qualcuno ha qualche idea cosa potrebbe andare storto o i prossimi passaggi per provare a eseguire il debug? Sono felice di pubblicare ulteriori informazioni se sarà di aiuto.

EDIT: Da allora ho (a mia conoscenza) trovato il modo di aggirare questo bug, anche se non sono più vicino a capire perché accade. Nel codice che genera le espressioni che ho postato sopra, ho avuto la seguente:

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()) 
    } 
); 

Tuttavia, non mi è piaciuta la compilazione di ogni espressione separatamente, in modo ho finito per fare via con la funzione WriteCollectionElements del tutto e solo creando il ciclo foreach in modo dinamico tramite Expression.Loop, Expression.Break, ecc.

Quindi, non sono più bloccato, ma ancora molto curioso.

+0

È il riproducibile eccezione? Accade sempre per lo stesso 'elemento'? –

+0

@DanielHilgarth Sì, l'eccezione si verifica ogni volta. Succede sempre quando si elabora lo stesso elemento, in questo caso il 20. – ChaseMedallion

+1

Forse è possibile creare una piccola applicazione di esempio con codice minimale che riproduca questo comportamento? –

risposta

1

Se si costruisce le azioni manualmente in C# ReSharper lamenta Lambda1 e lambda2 implicitamente catturando variabili nel 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; } 

in questo caso ReSharper afferma:
"chiusura Implicitamente catturato: lambda2" sull'espressione lambda2
"catturate implicitamente chiusura: lambda4" sull'espressione lambda1

la spiegazione di ciò è here e here. Se viene rimossa la riga su WriteCollectionElements, l'avviso scompare. In sostanza, la compilazione JIT crea una classe wrapper per le chiamate di espressioni interne, acquisendo i VALORI del writer e del tipo anonimo allo scopo di passare l'azione per BeginWritingCollection al metodo statico WriteCollectionElements.

La soluzione sarebbe quella di inline le dichiarazioni lambda2 in 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

compila entrambe le serie di azioni e guarda la DLL in [reflector | dotPeek | justDecompile | ILSpy] e vedrai il tipo di wrapper creato. –

+0

Ho letto i collegamenti e capisco questo problema, ma non riesco a vedere come è correlato al mio uso delle espressioni (al contrario delle azioni/funzioni non elaborate) ... – ChaseMedallion

+0

Le espressioni vengono convertite nell'azione equivalente quando si compila l'espressione. Non è possibile "eseguire" un'espressione –

Problemi correlati