代码之家  ›  专栏  ›  技术社区  ›  Mark Brittingham

如何创建和访问作为参数在C中传递的匿名类的新实例?

  •  9
  • Mark Brittingham  · 技术社区  · 16 年前

    我创建了一个函数,它接受一个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:下面是匿名版本的返回列表函数。有人能告诉我为什么对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显示字段名-奇怪)。因此,我使用typescriptor.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字段访问。另外,感谢DaveMarkle帮助我了解更多关于使用activator.createObject()与GenuininitializedObject之间的权衡。

    3 回复  |  直到 14 年前
        1
  •  25
  •   jason    16 年前

    匿名类型封装了一组 只读的 性质。这说明

    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]);
    }
    
        2
  •  2
  •   Guillaume86    14 年前

    我也遇到了同样的问题,我通过创建一个新的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?)
            });
    

    做数据阅读器。从新方法中得到东西,第一个方法是:

    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之类的东西,我敢打赌速度会快得多;)

        3
  •  1
  •   Dave Markle    16 年前

    第2题:

    我不知道,但我倾向于使用activator.createObject()而不是formatterServices.getUninitializedObject(),因为您的对象可能创建不正确。getUninitializedObject()不会运行像createObject()这样的默认构造函数,而且您不一定知道t的黑盒中有什么…