我创建了一个函数,该函数接受 SQL 命令并生成可用于填充类实例列表的输出。该代码效果很好。我在这里包含了一个没有异常处理的稍微简化的版本,仅供引用 - 如果您想直接跳转问题,请跳过此代码。不过,如果您在这里有任何建议,我会洗耳恭听。
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# 语言的这个特定角落还比较陌生。
更新:我必须感谢 Jason 提供了解决此问题的关键。下面是修改后的代码,它将创建一个匿名类实例列表,从查询中填充每个实例的字段。
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 推荐的数据库字段访问。另外,还要感谢 Dave Markle 帮助我更多地了解使用 Activator.CreateObject() 与 GenUninitializedObject 的权衡。
最佳答案
匿名类型封装了一组只读 属性。这说明
为什么
Type.GetFields
在匿名类型上调用时返回一个空数组:匿名类型没有公共(public)字段。匿名类型的公共(public)属性是只读的,不能通过调用
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]);
}
关于c# - 如何创建和访问在 C# 中作为参数传递的匿名类的新实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/478013/