2009-01-25 23 views
9

Tôi đã tạo ra một hàm nhận lệnh SQL và tạo ra kết quả đầu ra có thể được sử dụng để điền vào một Danh sách các cá thể lớp. Mã hoạt động rất tốt. Tôi đã bao gồm một phiên bản hơi đơn giản mà không cần xử lý ngoại lệ ở đây chỉ để tham khảo - bỏ qua mã này nếu bạn muốn nhảy ngay vấn đề. Nếu bạn có gợi ý ở đây, mặc dù, tôi là tất cả tai.Làm cách nào để tạo và truy cập một cá thể mới của một Lớp ẩn danh được chuyển như một tham số trong 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; 
    } 

Như tôi đã nói, điều này chỉ hoạt động tốt. Tuy nhiên, tôi muốn có thể gọi một hàm tương tự với một lớp vô danh vì lý do hiển nhiên.

Câu hỏi # 1: có vẻ như tôi phải xây dựng một lớp vô danh dụ trong cuộc gọi đến phiên bản ẩn danh của chức năng này - có đúng không? Cuộc gọi mẫu là:

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

Câu hỏi # 2: phiên bản ẩn danh của hàm Danh sách quay lại của tôi bên dưới. Bất cứ ai có thể cho tôi biết lý do tại sao cuộc gọi đến info.SetValue chỉ đơn giản là không làm gì? Nó không trả về lỗi hoặc bất cứ điều gì nhưng nó cũng không thay đổi giá trị của trường mục tiêu.

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

Bất kỳ ý tưởng nào?

Lưu ý: khi tôi cố gắng sử dụng mảng FieldInfo như tôi đã làm trong hàm ở trên, mảng typeFields có phần tử 0 (mặc dù objectType hiển thị tên trường - lạ). Vì vậy, tôi sử dụng TypeDescriptor.GetProperties thay thế.

Bất kỳ lời khuyên và hướng dẫn nào khác về việc sử dụng các lớp phản chiếu hoặc ẩn danh đều thích hợp ở đây - Tôi tương đối mới với ngôn ngữ C# cụ thể này.

CẬP NHẬT: Tôi phải cảm ơn Jason về chìa khóa để giải quyết vấn đề này. Dưới đây là mã sửa đổi sẽ tạo ra một danh sách các cá thể lớp ẩn danh, điền vào các trường của mỗi cá thể từ một truy vấn.

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

Lưu ý rằng truy vấn đã được tạo và tham số được khởi tạo trong cuộc gọi trước đó với phương pháp của đối tượng này. Mã ban đầu có một tổ hợp vòng lặp bên trong/bên ngoài để người dùng có thể có các trường trong lớp ẩn danh của họ không khớp với một trường. Tuy nhiên, để đơn giản hóa thiết kế, tôi đã quyết định không cho phép điều này và thay vào đó đã chấp nhận truy cập trường db được Jason đề xuất. Ngoài ra, nhờ Dave Markle cũng giúp tôi hiểu thêm về sự cân bằng trong việc sử dụng Activator.CreateObject() so với GenUninitializedObject.

Trả lời

24

Các loại ẩn danh đóng gói một tập hợp chỉ đọc thuộc tính. Điều này giải thích

  1. trả về một mảng trống khi được gọi theo loại ẩn danh của bạn: các loại ẩn danh không có trường công khai.

  2. Thuộc tính công khai trên loại ẩn danh là chỉ đọc và không thể đặt giá trị của chúng bằng cách gọi đến PropertyInfo.SetValue. Nếu bạn gọi số PropertyInfo.GetSetMethod trên một loại tài sản ẩn danh, bạn sẽ nhận được null.

Trong thực tế, nếu bạn thay đổi

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

bạn sẽ nhận được một ngoại lệ thông báo cho bạn rằng phương pháp thiết lập bất động sản không thể được tìm thấy.

Bây giờ, để giải quyết vấn đề của bạn, những gì bạn có thể làm là sử dụng Activator.CreateInstance. Tôi xin lỗi vì tôi quá lười biếng để loại bỏ mã cho bạn, nhưng sau đây sẽ chứng minh cách sử dụng nó.

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

Vì vậy, chỉ cần chạy qua một vòng lặp, như bạn đã làm, để lấp đầy các mảng đối tượng mà bạn cần phải vượt qua để Activator.CreateInstance và sau đó gọi Activator.CreateInstance khi vòng lặp được thực hiện. Thứ tự tài sản quan trọng ở đây vì hai loại vô danh giống nhau nếu và chỉ khi chúng có cùng số thuộc tính với cùng một loại và cùng tên trong cùng một thứ tự.

Để biết thêm, hãy xem MSDN page về các loại ẩn danh.

Cuối cùng, và điều này thực sự là một sang một bên và không Gecman cho câu hỏi của bạn, nhưng đoạn mã sau

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

có thể được đơn giản hóa bằng cách

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

Jason - cảm ơn bạn! Tôi thực sự đánh giá cao mọi nỗ lực. Tôi đã không làm việc thông qua lời khuyên của bạn được nêu ra nhưng nó là chính xác những gì tôi đang tìm kiếm: đặc biệt là các thông tin về những hạn chế và Activator.CreateInstance. –

+1

Một điều cuối cùng: Tôi coi đơn giản hóa bạn đã cung cấp nhưng tôi chưa quyết định liệu tôi có yêu cầu có trường phù hợp trong truy vấn cho từng trường trong đối tượng không, vì vậy tôi vẫn linh hoạt ngay bây giờ. Cảm ơn, mặc dù, vẫn còn lời khuyên tốt. –

+1

Hmmm ... thực ra, với yêu cầu rằng có một trường trong hàm tạo cho mỗi trường trong đối tượng ban đầu, có áp lực hơn một chút để yêu cầu tất cả các trường đều khớp nhau (vì vậy tôi không phải thiết lập các giá trị giả cho các trường bị thiếu, v.v.). –

1

Câu hỏi # 2:

Tôi thật sự không biết, nhưng tôi sẽ có xu hướng sử dụng Activator.CreateObject() thay vì FormatterServices.GetUninitializedObject(), vì đối tượng của bạn có thể không được tạo đúng. GetUninitializedObject() sẽ không chạy một hàm tạo mặc định như CreateObject() sẽ, và bạn không nhất thiết phải biết những gì trong hộp đen của T ...

2

tôi đã cùng một vấn đề, tôi giải quyết nó bằng cách tạo một Linq.Expression mới sẽ thực hiện công việc thực tế và biên dịch nó thành một lambda: đây là mã của tôi, ví dụ:

Tôi muốn chuyển đổi cuộc gọi đó:

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

lời kêu gọi rằng:

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

và làm những điều DataReader.Get từ phương pháp mới, phương pháp đầu tiên là:

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

tôi phải xây dựng một biểu thức trong phương thức mới:

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

Thực hiện việc này theo cách đó, tôi hoàn toàn có thể tránh được Activator.CreateInstance hoặc công cụ FormatterServices.GetUninitializedObject, tôi đặt cược nó nhanh hơn rất nhiều;)

Các vấn đề liên quan