2012-09-05 30 views
7

我正在使用System.Ling.Expressions API創建和編譯一個表達式。編譯工作正常,但在某些情況下,當運行編譯的lambda時,我會遇到無法解釋的NullReferenceExceptions甚至是System.Security.Verification異常。作爲參考,此項目的目的是爲.NET類型創建和編譯自定義序列化器函數。奇怪的異常編譯動態構建的表達式

以下是對於debuginfo軟拋出一個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) 
} 

異常被調用#Lambda3,這是從WriteCollectionElements反覆調用中拋出。 WriteCollectionElements的實現如下:

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

從這個函數內部調試,我已經確定,收集,作家的WriteAction和元素都是非空時,拋出異常。 ,我傳遞給編譯拉姆達的說法是:

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

而且奇怪的是,如果我刪除b屬性和重新生成我的串行功能,一切工作正常。在這種情況下,debuginfo軟的串行是:

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

我在Windows 7上運行.NET Framework 4(至少這是我的構建目標),VS快遞C#2010

沒有人有任何想法什麼可能會出錯或嘗試調試的下一步?如果能提供幫助,我很樂意發佈更多信息。

編輯:我自從(據我所知)找到了解決這個bug的方法,儘管我沒有接近理解它爲什麼會發生。在生成我上面張貼的表達式的代碼,我有以下幾點:

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

但是,我不喜歡單獨編制的每一個表情,所以我最終乾脆只用WriteCollectionElements功能做掉通過Expression.Loop,Expression.Break等動態創建foreach循環。

因此,我不再被阻止,但仍然非常好奇。

+0

是例外可再現?它是否總是發生在同一個「元素」? –

+0

@DanielHilgarth是的,每次都會發生異常。在處理相同的元素時總是會發生這種情況,在本例中爲20. – ChaseMedallion

+1

也許您可以使用最少的代碼創建一個小的示例應用程序來重現此行爲? –

回答

1

如果你建立手動在C#ReSharper的抱怨在這種情況下,ReSharper的狀態在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; } 

Lambda1和Lambda2隱含捕捉變量的操作:在lambda2表達:
「lambda2含蓄抓獲關閉」在lambda1表達

對此的解釋是herehere
「lambda4隱式捕捉閉合」。如果刪除WriteCollectionElements行,則警告消失。實質上,JIT編譯爲內部表達式調用創建一個包裝類,捕獲作者和匿名類型的VALUES,以便將BeginWritingCollection的操作傳遞給WriteCollectionElements靜態方法。

的解決辦法是從lambda2報表內嵌到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

編譯這兩組動作並查看DLL中的[reflector | dotPeek | justDecompile | ILSpy],你會看到創建的包裝類型。 –

+0

我閱讀了鏈接並理解了這個問題,但是我沒有看到它與我使用表達式(與原始動作/函數相反)如何相關... – ChaseMedallion

+0

編譯表達式時,表達式會轉換爲等效動作。你不能「執行」一個表達式 –