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

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

  •  11
  • Mark Brittingham  · 技术社区  · 17 年前

    我创建了一个函数,该函数接受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函数的匿名版本如下。有人能告诉我为什么打电话给信息。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。相反,获取属性。

    关于使用反射或匿名类的任何其他提示和指导在这里都是合适的——我对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推荐的db字段访问。此外,还要感谢Dave Markle帮助我更多地了解使用Activator的权衡。CreateObject()与GenUninitializedObject。

    4 回复  |  直到 17 年前
        1
  •  25
  •   jason    17 年前

    匿名类型封装了一组 只读的 物业。这解释了

    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 });
    

    所以,就像你所做的那样,运行一个循环来填充你需要传递的对象数组 激活器。方法 然后打电话 激活器。方法 当循环完成时。属性顺序在这里很重要,因为两个匿名类型是相同的,当且仅当它们具有相同数量的具有相同类型和相同名称的属性时,它们才是相同的。

    有关更多信息,请参阅 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    15 年前

    我也遇到了同样的问题,我通过创建一个新的Linq来解决它。将执行实际工作并将其编译为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。从新方法中获取东西,第一种方法是:

    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);
            }
    

    这样做,我可以完全避开激活器。CreateInstance或格式化服务。GetUninitializedObject的东西,我打赌它快得多;)

        3
  •  1
  •   Dave Markle    17 年前

    问题2:

    我真的不知道,但我倾向于使用Activator。CreateObject()而不是FormatterServices。GetUninitializedObject(),因为您的对象可能没有正确创建。GetUninitializedObject()不会像CreateObject()那样运行默认构造函数,而且你不一定知道t的黑盒子里有什么。。。