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.
È il riproducibile eccezione? Accade sempre per lo stesso 'elemento'? –
@DanielHilgarth Sì, l'eccezione si verifica ogni volta. Succede sempre quando si elabora lo stesso elemento, in questo caso il 20. – ChaseMedallion
Forse è possibile creare una piccola applicazione di esempio con codice minimale che riproduca questo comportamento? –