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