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.
¿El reproducible excepción? ¿Está sucediendo siempre para el mismo 'elemento'? –
@DanielHilgarth Sí, la excepción ocurre siempre. Siempre sucede cuando se procesa el mismo elemento, en este caso el 20. – ChaseMedallion
¿Tal vez puede crear una pequeña aplicación de muestra con un código mínimo que reproduzca este comportamiento? –