Edit: Ho cambiato il codice per utilizzare il costruttore tupla invece di Tuple.Create. Attualmente funziona solo per un massimo di 8 valori, ma per aggiungere lo 'stacking Tuple' dovrebbe essere banale.
Questo è un po 'complicato e l'implementazione è in genere dipendente dall'origine dati. Per dare un'impressione, ho creato una soluzione utilizzando un elenco di tipi anonimi come fonte.
Come diceva Elion, abbiamo bisogno di creare dinamicamente un albero di espressioni per chiamarlo in seguito. La tecnica di base che impieghiamo è chiamata proiezione.
Dobbiamo ottenere in fase di esecuzione le informazioni sul tipo e creare un CostruttoreInfor del costruttore Tupla (...) in base al conteggio delle proprietà. Questo è dinamico (anche se deve essere lo stesso per record) per ogni chiamata.
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;
}
}
Se si desidera utilizzare un DataReader o alcuni input CVS, si avrebbe bisogno di riscrivere la funzione di GetTupleNewExpression.
Non parlo delle prestazioni, anche se non dovrebbe essere molto più lento di un'implementazione LINQ nativa poiché la generazione dell'espressione LINQ avviene solo una volta per chiamata. Se è troppo lento si può andare giù per la strada di generare codice (e tenerlo memorizzato in un file) per esempio usando Mono.Cecil.
Non ho ancora potuto testarlo in C# 4.0 ma dovrebbe funzionare. Se volete provare in C# 3.5 è necessario il seguente codice così:
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'è un limite al numero di membri in una tupla. Cosa dovrebbe fare il codice se ci sono troppi membri? – Eilon
Non c'è limite - La tupla di 8 elementi è progettata in modo tale da avere l'ottavo elemento come un'altra tupla – Yurik
Hmno, non vi è alcun beneficio dalla digitazione dinamica dei dati non tipizzati. Sceglierai in modo dinamico il tipo sbagliato. –