5

リモート処理のシナリオでは、結果は Tuple オブジェクトの配列またはリストとして受け取るのに非常に適しています (強力な型指定の利点の 1 つです)。

例: 動的に変換SELECT Name, Age FROM Table => List<Tuple<string,int>>

List<Tuple<...>>質問: 任意のデータ テーブル (SQL 結果セットや CSV ファイルなど) が与えられ、各列の型が実行時にのみわかっている場合に、厳密に型指定されたオブジェクトを動的に作成するコードを生成するサンプルはありますか。コードは動的に生成する必要があります。そうしないと、非常に遅くなります。

4

2 に答える 2

11

編集: Tuple.Create の代わりにTupleコンストラクターを使用するようにコードを変更しました。現在、最大 8 つの値に対してのみ機能しますが、「タプル スタッキング」を追加するのは簡単です。


これは少しトリッキーで、実装はデータソースに依存しています。印象を与えるために、匿名型のリストをソースとして使用してソリューションを作成しました。

Elion が言ったように、後で呼び出す式ツリーを動的に作成する必要があります。私たちが採用する基本的な手法は、投影と呼ばれます。

実行時に型情報を取得し、プロパティ数に従ってTuple(...)コンストラクターの ConstructorInfor を作成する必要があります。これは、呼び出しごとに動的です (ただし、レコードごとに同じである必要があります)。

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 式の生成は呼び出しごとに 1 回しか行われないため、ネイティブ 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);
    }
}
于 2010-01-06T14:00:02.423 に答える
0

Dominik が IEnumerable を反復処理するときに Tuple を遅延作成する式を作成したことに非常に感銘を受けましたが、私の状況では、彼の概念のいくつかを別の方法で使用する必要がありました。

実行時にデータ型のみを知っている状態で、DataReader から Tuple にデータをロードしたいと考えています。この目的のために、次のクラスを作成しました。

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

ドミニクのソリューションとの違いのいくつか:

1) 遅延読み込みなし。一度に IDataReader から IDataRecord の 1 つのレコードを使用するため、遅延読み込みの利点はわかりませんでした。

2) IQueryable はありません。代わりにオブジェクトを出力します。型の安全性が失われているため、これは欠点と見なされる可能性がありますが、私がそれをどのように使用しているかは、実際には不利ではないことがわかりました。DataRecord を取得するためにクエリを実行した場合、型のパターンが何であるかを知っている可能性があるため、オブジェクトが返された直後に厳密に型指定された Tuple に直接キャストできます。

私が取り組んでいる別のユースケース (コードはまだ流動的であるため投稿されていません) では、複数の結合を使用して選択クエリから構築された複数のオブジェクトを表すいくつかの返されたタプルが必要でした。複数行のクエリ結果を不変オブジェクトに処理すると、DataReader を反復処理するときにサブタイプの配列を設定するため、インピーダンスの不一致が生じることがあります。私は過去に、ビルド中にプライベートな可変クラスを作成し、移入が完了したときに不変オブジェクトを作成することでこれを解決しました。この DynamicTuple により、いくつかの異なるクエリで使用する概念を汎用関数に抽象化して、任意の結合されたクエリを読み取り、それを専用のプライベート クラスの代わりに (DynamicTuples の) List に構築し、それを使用して不変を構築できます。データ オブジェクト。

于 2016-04-07T21:06:11.053 に答える