2012-09-05 11 views
7

Estoy creando y compilando una expresión con la API System.Ling.Expressions. La compilación funciona bien, pero en algunos casos obtengo inexplicables excepciones NullReferenceExceptions o incluso System.Security.Verification al ejecutar el lambda compilado. Como referencia, el propósito de este proyecto es crear y compilar una función de serializador personalizado para un tipo .NET.Excepciones extrañas compiladas de forma dinámica expresión

El siguiente es el DebugInfo de una expresión que lanza 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) 
} 

La excepción se produce dentro de la llamada a # lambda3, que se llama repetidamente desde WriteCollectionElements. La implementación de WriteCollectionElements es el siguiente:

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

De depuración dentro de esta función, he determinado que la recolección, escritor, writeAction, y el elemento son todos los no-nula cuando se produce la excepción. El argumento de que estoy pasando a la lambda compilado es:

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

También extraño es que si quito la propiedad b y volver a generar mi función serializador, todo funciona bien. En este caso el DebugInfo para el serializador es:

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

Me postulo .NET Framework 4 (al menos esa es mi tipo de generación) en Windows 7, VS expreso C# 2010.

¿Alguien tiene alguna idea ¿Qué podría estar yendo mal o los siguientes pasos para intentar depurar? Me complace publicar más información si ayuda.

EDIT: Desde entonces (según mi conocimiento) encontré mi camino alrededor de este error, aunque no estoy más cerca de entender por qué sucede. En el código que genera las expresiones que he publicado anteriormente, tenía el siguiente:

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

Sin embargo, no me gustó la compilación de toda expresión por separado, así que terminé la supresión de la función WriteCollectionElements por completo y sólo creando el bucle foreach dinámicamente a través de Expression.Loop, Expression.Break, etc.

Por lo tanto, ya no estoy bloqueado, pero todavía tengo mucha curiosidad.

+0

¿El reproducible excepción? ¿Está sucediendo siempre para el mismo 'elemento'? –

+0

@DanielHilgarth Sí, la excepción ocurre siempre. Siempre sucede cuando se procesa el mismo elemento, en este caso el 20. – ChaseMedallion

+1

¿Tal vez puede crear una pequeña aplicación de muestra con un código mínimo que reproduzca este comportamiento? –

Respuesta

1

Si usted construye las acciones manualmente en C# ReSharper se queja de Lambda1 y LAMBDA2 implícitamente captura de variables en el 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; } 

En este caso ReSharper estados:
"cierre Implícitamente capturado: LAMBDA2" en la expresión LAMBDA2
"Implícitamente capturaron cierre: lambda4" en la expresión lambda1

la explicación para esto es here y here. Si se elimina la línea a WriteCollectionElements, la advertencia desaparece. Básicamente, la compilación JIT crea una clase contenedora para las llamadas de expresión internas, capturando los VALORES del escritor y el tipo anónimo con el fin de entregar la acción para BeginWritingCollection al método estático WriteCollectionElements.

La solución sería inline las declaraciones de LAMBDA2 en 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

compilar ambos conjuntos de acciones y mirar la DLL en [reflector | dotPeek | justDecompile | ILSpy] y verá el tipo de envoltura creado. –

+0

He leído los enlaces y entiendo este problema, pero no veo cómo se relaciona con mi uso de Expressions (a diferencia de Raw Actions/Funcs) ... – ChaseMedallion

+0

Tus expresiones se convierten en la acción equivalente cuando compilas la expresión. No puede "ejecutar" una expresión –

Cuestiones relacionadas