2009-01-25 38 views
9

我已經創建了一個函數,它接受一個SQL命令併產生可用於填充類實例列表的輸出。代碼很好。我已經包含了一個稍微簡化的版本,但沒有異常處理,僅供參考 - 如果您想跳過此問題,請跳過此代碼。如果你在這裏有建議,但我是全部耳朵。如何創建並訪問在C#中作爲參數傳遞的匿名類的新實例?

public List<T> ReturnList<T>() where T : new() 
    { 
     List<T> fdList = new List<T>(); 
     myCommand.CommandText = QueryString; 
     SqlDataReader nwReader = myCommand.ExecuteReader(); 
     Type objectType = typeof (T); 
     FieldInfo[] typeFields = objectType.GetFields(); 
     while (nwReader.Read()) 
     { 
      T obj = new T(); 
      foreach (FieldInfo info in typeFields) 
      { 
       for (int i = 0; i < nwReader.FieldCount; i++) 
       { 
        if (info.Name == nwReader.GetName(i)) 
        { 
         info.SetValue(obj, nwReader[i]); 
         break; 
        } 
       } 
      } 
      fdList.Add(obj); 
     } 
     nwReader.Close(); 
     return fdList; 
    } 

正如我所說,這工作得很好。不過,我希望能夠通過匿名類別調用類似的功能,原因很明顯。

問題1:看來我必須在我的匿名版本的函數中調用匿名類實例 - 這是正確的嗎?一個示例調用是:

.ReturnList(new { ClientID = 1, FirstName = "", LastName = "", Birthdate = DateTime.Today }); 

問題2:我的ReturnList函數的匿名版本如下。任何人都可以告訴我爲什麼對info.SetValue的調用根本沒有做什麼?它不會返回錯誤或任何內容,但它也不會更改目標字段的值。

public List<T> ReturnList<T>(T sample) 
    { 
     List<T> fdList = new List<T>(); 
     myCommand.CommandText = QueryString; 
     SqlDataReader nwReader = myCommand.ExecuteReader(); 
     // Cannot use FieldInfo[] on the type - it finds no fields. 
     var properties = TypeDescriptor.GetProperties(sample); 
     while (nwReader.Read()) 
     { 
      // No way to create a constructor so this call creates the object without calling a ctor. Could this be a source of the problem? 
      T obj = (T)FormatterServices.GetUninitializedObject(typeof(T)); 
      foreach (PropertyDescriptor info in properties) 
      { 
       for (int i = 0; i < nwReader.FieldCount; i++) 
       { 
        if (info.Name == nwReader.GetName(i)) 
        { 
         // This loop runs fine but there is no change to obj!! 
         info.SetValue(obj, nwReader[i]); 
         break; 
        } 
       } 
      } 
      fdList.Add(obj); 
     } 
     nwReader.Close(); 
     return fdList; 
    } 

任何想法?

Note:當我嘗試使用FieldInfo數組,就像我在上面的函數中那樣,typeFields數組有零個元素(即使objectType顯示字段名稱 - 奇怪)。因此,我改用TypeDescriptor.GetProperties。

任何其他有關使用反射或匿名類的提示和指導都適用於此 - 我對C#語言這個特定的角落相對較新。

更新:我必須感謝傑森解決這個問題的關鍵。下面是修改後的代碼,它將創建一個匿名類實例列表,填充查詢中每個實例的字段。

public List<T> ReturnList<T>(T sample) 
    { 
     List<T> fdList = new List<T>(); 
     myCommand.CommandText = QueryString; 
     SqlDataReader nwReader = myCommand.ExecuteReader(); 
     var properties = TypeDescriptor.GetProperties(sample); 
     while (nwReader.Read()) 
     { 
      int objIdx = 0; 
      object[] objArray = new object[properties.Count]; 
      foreach (PropertyDescriptor info in properties) 
       objArray[objIdx++] = nwReader[info.Name]; 
      fdList.Add((T)Activator.CreateInstance(sample.GetType(), objArray)); 
     } 
     nwReader.Close(); 
     return fdList; 
    } 

請注意,查詢已構造並且參數已在以前調用此對象的方法時初始化。原始代碼有一個內部/外部循環組合,以便用戶可以在其匿名類中的字段與字段不匹配。然而,爲了簡化設計,我決定不允許這樣做,而是採用Jason推薦的db字段訪問方式。此外,感謝Dave Markle以及幫助我​​更好地瞭解使用Activator.CreateObject()與GenUninitializedObject進行權衡。

回答

24

匿名類型封裝了一組只讀屬性。這就解釋了

  1. 爲什麼Type.GetFields返回叫上您的匿名類型時,一個空數組:匿名類型不具有公共字段。

  2. 匿名類型的公共屬性是隻讀的,不能通過調用PropertyInfo.SetValue來設置它們的值。如果您以匿名方式致電PropertyInfo.GetSetMethod屬性,您將收到返回null

事實上,如果你改變

var properties = TypeDescriptor.GetProperties(sample); 
while (nwReader.Read()) { 
    // No way to create a constructor so this call creates the object without calling a ctor. Could this be a source of the problem? 
    T obj = (T)FormatterServices.GetUninitializedObject(typeof(T)); 
    foreach (PropertyDescriptor info in properties) { 
     for (int i = 0; i < nwReader.FieldCount; i++) { 
      if (info.Name == nwReader.GetName(i)) { 
       // This loop runs fine but there is no change to obj!! 
       info.SetValue(obj, nwReader[i]); 
       break; 
      } 
     } 
    } 
    fdList.Add(obj); 
} 

PropertyInfo[] properties = sample.GetType().GetProperties(); 
while (nwReader.Read()) { 
    // No way to create a constructor so this call creates the object without calling a ctor. Could this be a source of the problem? 
    T obj = (T)FormatterServices.GetUninitializedObject(typeof(T)); 
    foreach (PropertyInfo info in properties) { 
     for (int i = 0; i < nwReader.FieldCount; i++) { 
      if (info.Name == nwReader.GetName(i)) { 
       // This loop will throw an exception as PropertyInfo.GetSetMethod fails 
       info.SetValue(obj, nwReader[i], null); 
       break; 
      } 
     } 
    } 
    fdList.Add(obj); 
} 

,您將收到一個異常,通知您的屬性設置方法不能發現。

現在,要解決您的問題,您可以做的是使用Activator.CreateInstance。對不起,我懶得爲你輸入代碼,但下面將演示如何使用它。

var car = new { Make = "Honda", Model = "Civic", Year = 2008 }; 
var anothercar = Activator.CreateInstance(car.GetType(), new object[] { "Ford", "Focus", 2005 }); 

所以只是通過一個循環運行,爲你做,以填補你需要傳遞給Activator.CreateInstance,然後調用Activator.CreateInstance當循環完成的對象數組。屬性順序在這裏很重要,因爲兩個匿名類型是相同的,當且僅當它們具有相同數量的具有相同類型和相同名稱的相同順序的屬性。

有關詳細信息,請參見匿名類型的MSDN page

最後,這是一個真正的一邊,而不是密切相關的問題,但下面的代碼

foreach (PropertyDescriptor info in properties) { 
    for (int i = 0; i < nwReader.FieldCount; i++) { 
     if (info.Name == nwReader.GetName(i)) { 
      // This loop runs fine but there is no change to obj!! 
      info.SetValue(obj, nwReader[i]); 
      break; 
     } 
    } 
} 

可以通過

foreach (PropertyDescriptor info in properties) { 
      info.SetValue(obj, nwReader[info.Name]); 
} 
+1

傑森 - 謝謝!我非常感謝所有的努力。我還沒有通過你的建議,但它正是我所期待的:特別是關於限制和Activator.CreateInstance的信息。 – 2009-01-25 23:25:35

1

問題2:

我真的不知道,但我會傾向於使用Activator.CreateObject()代替FormatterServices.GetUninitializedObject(),因爲你的對象可能無法正確創建。 GetUninitializedObject()不會運行像CreateObject()這樣的默認構造函數,並且您不一定知道T的黑盒子裏有什麼...

2

我有同樣的問題簡單化,我解決了它通過創建一個新的Linq.Expression來完成真正的工作並將其編譯爲lambda:這裏是我的代碼,例如:

我想轉換該調用:

var customers = query.ToList(r => new 
      { 
       Id = r.Get<int>("Id"), 
       Name = r.Get<string>("Name"), 
       Age = r.Get<int>("Age"), 
       BirthDate = r.Get<DateTime?>("BirthDate"), 
       Bio = r.Get<string>("Bio"), 
       AccountBalance = r.Get<decimal?>("AccountBalance"), 
      }); 

這一號召:

var customers = query.ToList(() => new 
     { 
      Id = default(int), 
      Name = default(string), 
      Age = default(int), 
      BirthDate = default(DateTime?), 
      Bio = default(string), 
      AccountBalance = default(decimal?) 
     }); 

和新方法做事情DataReader.Get,第一種方法是:

public List<T> ToList<T>(FluentSelectQuery query, Func<IDataReader, T> mapper) 
    { 
     return ToList<T>(mapper, query.ToString(), query.Parameters); 
    } 

我必須建立在一個表達式新方法:

public List<T> ToList<T>(Expression<Func<T>> type, string sql, params object[] parameters) 
     { 
      var expression = (NewExpression)type.Body; 
      var constructor = expression.Constructor; 
      var members = expression.Members.ToList(); 

      var dataReaderParam = Expression.Parameter(typeof(IDataReader)); 
      var arguments = members.Select(member => 
       { 
        var memberName = Expression.Constant(member.Name); 
        return Expression.Call(typeof(Utilities), 
              "Get", 
              new Type[] { ((PropertyInfo)member).PropertyType }, 
              dataReaderParam, memberName); 
       } 
      ).ToArray(); 

      var body = Expression.New(constructor, arguments); 

      var mapper = Expression.Lambda<Func<IDataReader, T>>(body, dataReaderParam); 

      return ToList<T>(mapper.Compile(), sql, parameters); 
     } 

這樣做這樣,我可以完全避免Activator.CreateInstance或FormatterServices.GetUninitializedObject的東西,我敢打賭它快很多;)

相關問題