2010-01-05 31 views
5

Đối với kịch bản truy cập từ xa, kết quả sẽ rất tốt để nhận dưới dạng mảng hoặc danh sách đối tượng Tuple (trong số lợi ích đang gõ mạnh)..Net 4: Cách dễ dàng để tự động tạo Danh sách <Tuple<...>> Kết quả

Ví dụ: động chuyển đổi SELECT Name, Age FROM Table =>List<Tuple<string,int>>

Câu hỏi: có bất kỳ mẫu ra khỏi đó, đưa ra một bảng tùy ý các dữ liệu (như SQL resultset hoặc tập tin CSV), với các loại của mỗi cột duy nhất được biết trong thời gian chạy, để tạo mã sẽ tự động tạo đối tượng List<Tuple<...>> được đánh mạnh. Mã nên được tạo động, nếu không nó sẽ rất chậm.

+0

Có giới hạn về số lượng thành viên trong Tuple. Mã nên làm gì nếu có quá nhiều thành viên? – Eilon

+0

Không có giới hạn - Tuple gồm 8 phần tử được thiết kế theo cách sao cho có phần tử thứ 8 như một Tuple – Yurik

+1

Hmno khác, không có lợi ích nào khi nhập dữ liệu chưa được nhập động. Bạn sẽ tự động chọn loại sai. –

Trả lời

9

Chỉnh sửa: Tôi đã thay đổi mã để sử dụng hàm tạo Tuple thay vì Tuple.Create. Hiện tại, nó chỉ hoạt động với tối đa 8 giá trị, nhưng để thêm 'xếp chồng Tuple' sẽ không đáng kể.


Đây là một chút khó khăn và triển khai là loại phụ thuộc vào nguồn dữ liệu. Để tạo ấn tượng, tôi đã tạo một giải pháp bằng cách sử dụng danh sách các loại ẩn danh làm nguồn.

Như Elion đã nói, chúng ta cần tạo động một cây biểu thức để gọi nó sau đó. Kỹ thuật cơ bản mà chúng tôi sử dụng được gọi là chiếu .

Chúng tôi phải nhận thông tin loại thời gian chạy và tạo ConstructorInfor của số Tuple (...) hàm tạo theo số lượng thuộc tính. Đây là động (mặc dù cần phải giống nhau cho mỗi bản ghi) cho mỗi cuộc gọi.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 

class Program 
{ 
    static void Main(string[] args) 
    { 

     var list = new[] 
         { 
          //new {Name = "ABC", Id = 1}, 
          //new {Name = "Xyz", Id = 2} 
          new {Name = "ABC", Id = 1, Foo = 123.22}, 
          new {Name = "Xyz", Id = 2, Foo = 444.11} 
         }; 

     var resultList = DynamicNewTyple(list); 

     foreach (var item in resultList) 
     { 
      Console.WriteLine(item.ToString()); 
     } 

     Console.ReadLine(); 

    } 

    static IQueryable DynamicNewTyple<T>(IEnumerable<T> list) 
    { 
     // This is basically: list.Select(x=> new Tuple<string, int, ...>(x.Name, x.Id, ...); 
     Expression selector = GetTupleNewExpression<T>(); 

     var expressionType = selector.GetType(); 
     var funcType = expressionType.GetGenericArguments()[0]; // == Func< <>AnonType..., Tuple<String, int>> 
     var funcTypegenericArguments = funcType.GetGenericArguments(); 

     var inputType = funcTypegenericArguments[0]; // == <>AnonType... 
     var resultType = funcTypegenericArguments[1]; // == Tuple<String, int> 

     var selects = typeof (Queryable).GetMethods() 
      .AsQueryable() 
      .Where(x => x.Name == "Select" 
      ); 

     // This is hacky, we just hope the first method is correct, 
     // we should explicitly search the correct one 
     var genSelectMi = selects.First(); 
     var selectMi = genSelectMi.MakeGenericMethod(new[] {inputType, resultType}); 

     var result = selectMi.Invoke(null, new object[] {list.AsQueryable(), selector}); 
     return (IQueryable) result; 

    } 

    static Expression GetTupleNewExpression<T>() 
    { 
     Type paramType = typeof (T); 
     string tupleTyneName = typeof (Tuple).AssemblyQualifiedName; 
     int propertiesCount = paramType.GetProperties().Length; 

     if (propertiesCount > 8) 
     { 
      throw new ApplicationException(
       "Currently only Tuples of up to 8 entries are alowed. You could change this code to allow stacking of Tuples!"); 
     } 

     // So far we have the non generic Tuple type. 
     // Now we need to create select the correct geneeric of Tuple. 
     // There might be a cleaner way ... you could get all types with the name 'Tuple' and 
     // select the one with the correct number of arguments ... that exercise is left to you! 
     // We employ the way of getting the AssemblyQualifiedTypeName and add the genric information 
     tupleTyneName = tupleTyneName.Replace("Tuple,", "Tuple`" + propertiesCount + ","); 
     var genericTupleType = Type.GetType(tupleTyneName); 

     var argument = Expression.Parameter(paramType, "x"); 

     var parmList = new List<Expression>(); 
     List<Type> tupleTypes = new List<Type>(); 

     //we add all the properties to the tuples, this only will work for up to 8 properties (in C#4) 
     // We probably should use our own implementation. 
     // We could use a dictionary as well, but then we would need to rewrite this function 
     // more or less completly as we would need to call the 'Add' function of a dictionary. 
     foreach (var param in paramType.GetProperties()) 
     { 
      parmList.Add(Expression.Property(argument, param)); 
      tupleTypes.Add(param.PropertyType); 
     } 

     // Create a type of the discovered tuples 
     var tupleType = genericTupleType.MakeGenericType(tupleTypes.ToArray()); 

     var tuplConstructor = 
      tupleType.GetConstructors().First(); 

     var res = 
      Expression.Lambda(
       Expression.New(tuplConstructor, parmList.ToArray()), 
       argument); 

     return res; 
    } 
} 

Nếu bạn muốn sử dụng một DataReader hoặc một số đầu vào CVS, bạn sẽ cần phải viết lại các chức năng GetTupleNewExpression.

Tôi không thể nói về hiệu suất, mặc dù nó không nên chậm hơn nhiều khi triển khai LINQ gốc do việc tạo biểu thức LINQ chỉ xảy ra một lần cho mỗi cuộc gọi. Nếu quá chậm, bạn có thể đi xuống đường tạo mã (và giữ nó trong một tệp) ví dụ sử dụng Mono.Cecil.

Tôi chưa thể kiểm tra điều này trong C# 4.0 và chưa hoạt động. Nếu bạn muốn thử nó trong C# 3.5 bạn cần đoạn mã sau cũng như:

public static class Tuple 
{ 

    public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2) 
    { 
     return new Tuple<T1, T2>(item1, item2); 
    } 

    public static Tuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3) 
    { 
     return new Tuple<T1, T2, T3>(item1, item2, item3); 
    } 
} 

public class Tuple<T1, T2> 
{ 

    public Tuple(T1 item1, T2 item2) 
    { 
     Item1 = item1; 
     Item2 = item2; 
    } 

    public T1 Item1 { get; set;} 
    public T2 Item2 { get; set;} 

    public override string ToString() 
    { 
     return string.Format("Item1: {0}, Item2: {1}", Item1, Item2); 
    } 

} 

public class Tuple<T1, T2, T3> : Tuple<T1, T2> 
{ 
    public T3 Item3 { get; set; } 

    public Tuple(T1 item1, T2 item2, T3 item3) : base(item1, item2) 
    { 
     Item3 = item3; 
    } 

    public override string ToString() 
    { 
     return string.Format(base.ToString() + ", Item3: {0}", Item3); 
    } 
} 
+0

Dominik, bài đăng tuyệt vời, cảm ơn! Về việc tạo: .NET 4+ Tuple cho phép nhiều hơn 8 tham số - Tuple <8> là trường hợp đặc biệt - bạn có thể đặt giá trị thứ 8 là Tuple và Tuple <8> sẽ xử lý GetHashCode và so sánh đúng cách. Một lưu ý mặc dù - chúng ta nên sử dụng Tuple constructor, không phải là các phương thức tĩnh để làm việc trên. Tôi có nên sửa mã hay bạn muốn làm danh dự không? :) – Yurik

+0

Một điều nữa - vì lý do hiệu suất, nó sẽ là lý tưởng để có một chức năng biên dịch sẵn và lưu trữ mà có một 'IEnumerable ' và spits ra hoặc 'List >' (dễ dàng hơn nhiều), hoặc một 'IEnumerable > '- khó hơn vì tôi nghi ngờ bạn có thể biểu thị 'lợi nhuận' bằng cách sử dụng các biểu thức, do đó, một lớp trạng thái có thể được yêu cầu. – Yurik

+0

Yurik, tôi đã thay đổi mã để sử dụng hàm tạo. Mã chỉ hỗ trợ 8 giá trị, do đó, việc xếp chồng các Tuple còn lại cho bạn :) –

0

Tôi đã khá ấn tượng với Dominik của việc xây dựng một biểu thức để uể oải tạo tuple như chúng ta lặp qua các IEnumerable, nhưng hoàn cảnh của tôi gọi để tôi sử dụng một số khái niệm của mình theo một cách khác.

Tôi muốn tải dữ liệu từ DataReader vào Tuple chỉ với việc biết các loại dữ liệu trong thời gian chạy. Để kết thúc này, tôi tạo ra lớp sau:

Public Class DynamicTuple 

Public Shared Function CreateTupleAtRuntime(ParamArray types As Type()) As Object 
    If types Is Nothing Then Throw New ArgumentNullException(NameOf(types)) 
    If types.Length < 1 Then Throw New ArgumentNullException(NameOf(types)) 
    If types.Contains(Nothing) Then Throw New ArgumentNullException(NameOf(types)) 

    Return CreateTupleAtRuntime(types, types.Select(Function(typ) typ.GetDefault).ToArray) 
End Function 

Public Shared Function CreateTupleAtRuntime(types As Type(), values As Object()) As Object 
    If types Is Nothing Then Throw New ArgumentNullException(NameOf(types)) 
    If values Is Nothing Then Throw New ArgumentNullException(NameOf(values)) 
    If types.Length < 1 Then Throw New ArgumentNullException(NameOf(types)) 
    If values.Length < 1 Then Throw New ArgumentNullException(NameOf(values)) 
    If types.Length <> values.Length Then Throw New ArgumentException("Both the type and the value array must be of equal length.") 

    Dim tupleNested As Object = Nothing 
    If types.Length > 7 Then 
     tupleNested = CreateTupleAtRuntime(types.Skip(7).ToArray, values.Skip(7).ToArray) 
     types(7) = tupleNested.GetType 
     ReDim Preserve types(0 To 7) 
     ReDim Preserve values(0 To 7) 
    End If 
    Dim typeCount As Integer = types.Length 

    Dim tupleTypeName As String = GetType(Tuple).AssemblyQualifiedName.Replace("Tuple,", "Tuple`" & typeCount & ",") 
    Dim genericTupleType = Type.[GetType](tupleTypeName) 
    Dim constructedTupleType = genericTupleType.MakeGenericType(types) 

    Dim args = types.Select(Function(typ, index) 
           If index = 7 Then 
            Return tupleNested 
           Else 
            Return values(index) 
           End If 
          End Function) 
    Try 
     Return constructedTupleType.GetConstructors().First.Invoke(args.ToArray) 
    Catch ex As Exception 
     Throw New ArgumentException("Could not map the supplied values to the supplied types.", ex) 
    End Try 
End Function 

Public Shared Function CreateFromIDataRecord(dataRecord As IDataRecord) As Object 
    If dataRecord Is Nothing Then Throw New ArgumentNullException(NameOf(dataRecord)) 
    If dataRecord.FieldCount < 1 Then Throw New InvalidOperationException("DataRecord must have at least one field.") 

    Dim fieldCount = dataRecord.FieldCount 
    Dim types(0 To fieldCount - 1) As Type 
    Dim values(0 To fieldCount - 1) As Object 
    For I = 0 To fieldCount - 1 
     types(I) = dataRecord.GetFieldType(I) 
    Next 
    dataRecord.GetValues(values) 

    Return CreateTupleAtRuntime(types, values) 
End Function 

End Class 

Một số khác biệt từ giải pháp Dominik của:

1) Không tải lười biếng. Vì chúng tôi sẽ sử dụng một bản ghi IDataRecord từ một IDataReader tại một thời điểm, tôi không thấy một lợi thế trong việc tải chậm.

2) Không có IQueryable, thay vào đó nó xuất ra một đối tượng. Điều này có thể được coi là một bất lợi vì bạn đang mất an toàn loại, nhưng tôi đã thấy rằng làm thế nào tôi đang sử dụng nó không thực sự bất lợi cho bạn. Nếu bạn đã thực thi một truy vấn để lấy DataRecord, bạn có thể biết mẫu của các kiểu là gì và vì vậy bạn có thể truyền trực tiếp nó vào một Tuple được gõ mạnh ngay sau khi Object trả về.

Đối với trường hợp sử dụng khác mà tôi đang làm việc (mã không được đăng bởi vì nó vẫn còn trong thông lượng), tôi muốn một vài bộ trả về đại diện cho nhiều đối tượng đang được xây dựng từ truy vấn chọn có nhiều kết nối. Đôi khi, việc xử lý kết quả truy vấn nhiều dòng vào một đối tượng bất biến có trở kháng không phù hợp vì bạn đang điền một mảng các kiểu con khi bạn đang lặp qua DataReader. Tôi đã giải quyết điều này trong quá khứ bằng cách có một lớp riêng có thể thay đổi trong khi xây dựng, sau đó tạo ra một đối tượng bất biến khi populating được thực hiện. DynamicTuple này cho phép tôi trừu tượng hóa khái niệm mà tôi sử dụng trên một số truy vấn khác nhau cho một chức năng có mục đích chung để đọc truy vấn tham gia tùy ý, xây dựng nó thành Danh sách (của DynamicTuples) thay vì các lớp riêng chuyên dụng, sau đó sử dụng nó để xây dựng không thay đổi Đối tượng dữ liệu.

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