[英]How do I create and access a new instance of an Anonymous Class passed as a parameter in C#?
我創建了一個函數,它接受一個SQL命令並生成輸出,然后可以用它來填充類實例的List。 代碼效果很好。 我在這里提供了一個稍微簡化的版本,無需異常處理,僅供參考 - 如果您想直接解決問題,請跳過此代碼。 如果你在這里有建議,我會全力以赴。
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;
}
有任何想法嗎?
注意:當我嘗試像上面函數中那樣使用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的權衡。
匿名類型封裝了一組只讀屬性。 這解釋了
為什么Type.GetFields
在您的匿名類型上調用時返回一個空數組:匿名類型沒有公共字段。
匿名類型上的公共屬性是只讀的,並且不能通過調用PropertyInfo.SetValue
來設置其值。 如果在匿名類型的PropertyInfo.GetSetMethod
上調用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頁面 。
最后,這真的是一個旁邊,與你的問題沒有密切關系,但以下代碼
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]);
}
我有同樣的問題,我通過創建一個新的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的東西,我敢打賭它快了很多;)
問題2:
我真的不知道,但我傾向於使用Activator.CreateObject()而不是FormatterServices.GetUninitializedObject(),因為您的對象可能無法正確創建。 GetUninitializedObject()不會運行像CreateObject()那樣的默認構造函數,而且你不一定知道T的黑盒子里有什么...
此方法將一行sql查詢存儲在匿名類型的變量中。 您必須將原型傳遞給方法。 如果在sql查詢中找不到匿名類型的任何屬性,則會使用prototype-value填充它。 C#為其匿名類創建構造函數,這些參數與(只讀)屬性具有相同的名稱。
public static T GetValuesAs<T>(this SqlDataReader Reader, T prototype)
{
System.Reflection.ConstructorInfo constructor = prototype.GetType().GetConstructors()[0];
object[] paramValues = constructor.GetParameters().Select(
p => { try { return Reader[p.Name]; }
catch (Exception) { return prototype.GetType().GetProperty(p.Name).GetValue(prototype); } }
).ToArray();
return (T)prototype.GetType().GetConstructors()[0].Invoke(paramValues);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.