2014-09-28 47 views
3

我有一些代碼使用IlGenerator.Emit創建並使用datareader填充通用對象。它工作的很好,但是我需要擴展它來在數據庫字段名稱包含下劃線時填充簡單的子對象。使用ILGenerator.Emit設置對象的子屬性

例如,名爲「Address_Line1」的數據庫字段應填充屬性Line1,該屬性是實體上Address屬性的屬性。在C#代碼,基本上...

Entity.Address.Line1 = "value from reader"; 

我試着寫C#代碼,並使用ILSpy嘗試識別IL代碼我應該寫,但我不斷收到內存錯誤等

下面的代碼包括:當前工作的IL代碼,我已經包含了我的代碼嘗試與評論。誰能幫我嗎?

public static DynamicBuilder<T> CreateBuilder(IDataRecord reader) 
{ 
    var result = new DynamicBuilder<T>(); 
    var method = new DynamicMethod("DynamicCreate", typeof(T), new Type[] { typeof(IDataReader) }, typeof(T), true); 

    var generator = method.GetILGenerator(); 

    generator.DeclareLocal(typeof(T)); 
    generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes)); 
    generator.Emit(OpCodes.Stloc_0); 

    var getValue = reader.GetType().GetMethod("get_Item", new Type[] { typeof(int) }); 

    for (int i = 0; i < reader.FieldCount; i++) 
    { 
     var name = reader.GetName(i).Split('_'); // MY CODE 
     var propertyInfo = typeof(T).GetProperty(name[0]); 

     if (propertyInfo != null && propertyInfo.GetSetMethod() != null) 
     { 
      var endIfLabel = generator.DefineLabel(); 

      generator.Emit(OpCodes.Ldarg_0); 
      generator.Emit(OpCodes.Ldc_I4, i); 
      generator.Emit(OpCodes.Callvirt, typeof(IDataRecord).GetMethod("IsDBNull")); 
      generator.Emit(OpCodes.Brtrue, endIfLabel); 

      generator.Emit(OpCodes.Ldloc_0); 
      generator.Emit(OpCodes.Ldarg_0); 
      generator.Emit(OpCodes.Ldc_I4, i); 
      generator.Emit(OpCodes.Callvirt, getValue); 

      if (propertyInfo.PropertyType.Name.ToLower().Contains("nullable")) 
       generator.Emit(OpCodes.Unbox_Any, GetNullableType(reader.GetFieldType(i))); 
      else 
       generator.Emit(OpCodes.Unbox_Any, reader.GetFieldType(i)); 

      // START MY CODE TO GET THE SUB PROPERTY 
      if (name.Length > 1) 
      { 
       generator.Emit(OpCodes.Callvirt, propertyInfo.GetGetMethod()); 
       propertyInfo = propertyInfo.PropertyType.GetProperty(name[1]); 
      } 
      // END MY CODE 

      generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod()); 
      generator.MarkLabel(endIfLabel); 
     } 
    } 

    generator.Emit(OpCodes.Ldloc_0); 
    generator.Emit(OpCodes.Ret); 

    result.handler = (Load)method.CreateDelegate(typeof(Load)); 
    return result; 
} 
+0

您是否嘗試發射這個代碼的組件,然後在其上運行peverify? – svick 2014-09-28 18:19:29

+0

針對dll運行peverify返回my.dll中的所有類和方法已驗證。 – WDuffy 2014-09-28 19:05:42

+0

不針對包含此代碼的DLL,針對發出的代碼。 – svick 2014-09-28 19:06:26

回答

3

這樣的代碼:

static Entity DynamicCreate(IDataReader reader) 
{ 
    var entity = new Entity(); 
    entity.Property = (int)reader[0]; 
    return entity; 
} 

被編譯爲IL,看起來完全像你發出的代碼(不重要的部分省略):

ldloc.0  // entity 
ldarg.0  // reader 
ldc.i4.0  
callvirt System.Data.IDataRecord.get_Item 
unbox.any System.Int32 
callvirt UserQuery+Entity.set_Property 

但如果添加了第二個物業訪問:

static Entity DynamicCreate(IDataReader reader) 
{ 
    var entity = new Entity(); 
    entity.SubEntity.Property = (int)reader[0]; 
    return entity; 
} 

那麼IL看起來是這樣的:

ldloc.0  // entity 
callvirt UserQuery+Entity.get_SubEntity 
ldarg.0  // reader 
ldc.i4.0  
callvirt System.Data.IDataRecord.get_Item 
unbox.any System.Int32 
callvirt UserQuery+SubEntity.set_Property 

注意,調用get_SubEntityldloc.0ldarg.0之間,不對以前set_Property就像在你的代碼,所以你必須在你的代碼中有動它。

你的代碼不起作用的原因是IL是一種基於堆棧的語言:當你調用一個無參數實例方法(如屬性getter)時,堆棧頂部的對象(這裏是unbox.any)將被用作其this,這不是你想要的。基本上,你的代碼試圖做這樣的事情:

entity.Property = ((int)reader[0]).SubEntity; 
+1

svick修復它:)非常感謝您的詳細迴應! – WDuffy 2014-09-28 19:31:38