.Net 4: Простой способ динамично создать Список <Кортеж <…>> результаты

Для сценария дистанционной работы результат был бы очень хорош для получения как массив или список объектов Кортежа (среди преимуществ, являющихся строгим контролем типов).

Пример: динамично преобразуйте SELECT Name, Age FROM Table => List<Tuple<string,int>>

Вопрос: есть ли любые образцы там, что, учитывая произвольную таблицу данных (как набор результатов SQL или файл CSV), с типами каждого столбца, известного только во времени выполнения, чтобы сгенерировать код, который динамично создал бы со строгим контролем типов List<Tuple<...>> объект. Код должен быть динамично сгенерирован, иначе это было бы чрезвычайно медленно.

5
задан Yurik 5 January 2010 в 22:03
поделиться

1 ответ

Изменить: Я изменил код, чтобы использовать конструктор Tuple вместо Tuple.Create . В настоящее время он работает только для 8 значений, но добавить «наложение кортежей» должно быть тривиально.


Это немного сложно, и реализация зависит от источника данных. Чтобы произвести впечатление, я создал решение, используя список анонимных типов в качестве источника.

Как сказала Элион, нам нужно динамически создать дерево выражений, чтобы потом его вызывать. Используемая нами основная техника называется проекцией .

Мы должны получить во время выполнения информацию о типе и создать ConstructorInfor конструктора Tuple (...) в соответствии с количеством свойств. Это динамично (хотя должно быть одинаковым для каждой записи) для каждого вызова.

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

Если вы хотите использовать DataReader или какой-либо ввод CVS, вам необходимо переписать функцию GetTupleNewExpression .

Я не могу говорить о производительности, хотя она не должна быть намного медленнее, чем собственная реализация LINQ, поскольку генерация выражения LINQ происходит только один раз за вызов. Если он слишком медленный, вы можете пойти по пути генерации кода (и сохранить его в файле), например, используя Mono.Cecil.

Я пока не мог протестировать это в C # 4.0, но он должен работать. Если вы хотите попробовать это в C # 3.5, вам также понадобится следующий код:

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);
    }
}
11
ответ дан 13 December 2019 в 19:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: