48

式ツリーを使用して、次のselectステートメントを動的に生成したいと思います。

var v = from c in Countries
        where c.City == "London"
        select new {c.Name, c.Population};

生成する方法を考え出しました

var v = from c in Countries
        where c.City == "London"
        select new {c.Name};

しかし、selectラムダで複数のプロパティを指定できるコンストラクター/オーバーロードが見つからないようです。

4

9 に答える 9

73

これは、前述のように、Reflection Emit と以下に示したヘルパー クラスを使用して実行できます。以下のコードは進行中の作業であるため、その価値を理解してください...「私のボックスで動作します」。SelectDynamic メソッド クラスは、静的拡張メソッド クラスでトスする必要があります。

予想どおり、型は実行時まで作成されないため、Intellisense は得られません。レイト バインド データ コントロールで適切に機能します。

public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
{
    Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
    Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);

    ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
    IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();

    Expression selector = Expression.Lambda(Expression.MemberInit(
        Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);

    return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType },
                 Expression.Constant(source), selector));
}



public static class LinqRuntimeTypeBuilder
{
    private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };
    private static ModuleBuilder moduleBuilder = null;
    private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();

    static LinqRuntimeTypeBuilder()
    {
        moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);
    }

    private static string GetTypeKey(Dictionary<string, Type> fields)
    {
        //TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
        string key = string.Empty;
        foreach (var field in fields)
            key += field.Key + ";" + field.Value.Name + ";";

        return key;
    }

    public static Type GetDynamicType(Dictionary<string, Type> fields)
    {
        if (null == fields)
            throw new ArgumentNullException("fields");
        if (0 == fields.Count)
            throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");

        try
        {
            Monitor.Enter(builtTypes);
            string className = GetTypeKey(fields);

            if (builtTypes.ContainsKey(className))
                return builtTypes[className];

            TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);

            foreach (var field in fields)                    
                typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);

            builtTypes[className] = typeBuilder.CreateType();

            return builtTypes[className];
        }
        catch (Exception ex)
        {
            log.Error(ex);
        }
        finally
        {
            Monitor.Exit(builtTypes);
        }

        return null;
    }


    private static string GetTypeKey(IEnumerable<PropertyInfo> fields)
    {
        return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
    }

    public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
    {
        return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
    }
}
于 2009-04-06T20:12:27.093 に答える
9

受け入れられた答えは非常に便利ですが、実際の匿名型に少し近いものが必要でした。

実際の匿名型には、読み取り専用プロパティ、すべての値を入力するためのコンストラクタ、各プロパティの値を比較するための Equals/GetHashCode の実装、および各プロパティの名前/値を含む ToString の実装があります。(匿名型の完全な説明については、https://msdn.microsoft.com/en-us/library/bb397696.aspxを参照してください。)

匿名クラスのその定義に基づいて、https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.csで動的な匿名型を生成するクラスを github に配置しました。このプロジェクトには、偽の匿名型が実際の型のように動作することを確認するための単体テストも含まれています。

これを使用する方法の非常に基本的な例を次に示します。

AnonymousTypeUtils.CreateObject(new Dictionary<string, object>
{
    { "a", 1 },
    { "b", 2 }
});

また、別のメモ: Entity Framework で動的匿名型を使用する場合、コンストラクターを "members" パラメーター セットで呼び出す必要があることがわかりました。例えば:

Expression.New(
    constructor: anonymousType.GetConstructors().Single(), 
    arguments: propertyExpressions,
    members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray()
); 

"members" パラメーターを含まない Expression.New のいずれかのバージョンを使用した場合、Entity Framework はそれを匿名型のコンストラクターとして認識しません。したがって、実際の匿名型のコンストラクター式にはその「メンバー」情報が含まれることを意味すると思います。

于 2015-01-25T19:12:32.553 に答える
2

ここで IQueryable-Extensions を使用できます。これは、「Ethan J. Brown」によって説明されたソリューションの実装です。

https://github.com/thiscode/DynamicSelectExtensions

拡張機能は、匿名型を動的に構築します。

次に、これを行うことができます:

var YourDynamicListOfFields = new List<string>(

    "field1",
    "field2",
    [...]

)
var query = query.SelectPartially(YourDynamicListOfFields);
于 2013-07-04T10:34:49.823 に答える
2

あなたがこれを達成できるとは思えません。あなたがそうするとき、あなたはselect new { c.Name, c.Population }実際のクラスを作成していないように見えますが。Reflector または生の IL でコンパイルされた出力を見ると、これを確認できます。

次のようなクラスがあります。

[CompilerGenerated]
private class <>c__Class {
  public string Name { get; set; }
  public int Population { get; set; }
}

(わかりました、とにかく、プロパティは実際には単なるget_Name()andset_Name(name)メソッドセットであるため、少しクリーンアップしました)

あなたがしようとしているのは、適切な動的クラスの作成です.NET 4.0が登場するまで利用できないものです(それでも、あなたが望むものを達成できるかどうかはわかりません)。

最善の解決策は、さまざまな匿名クラスを定義してから、作成するクラスを決定するために何らかの論理チェックを行い、それを作成して object を使用できるようにすることですSystem.Linq.Expressions.NewExpression

ただし、基礎となる LINQ プロバイダーについて本当にハードコアになっている場合は、(少なくとも理論的には) 可能かもしれません。独自の LINQ プロバイダーを作成している場合は、現在解析されている式が Select であるかどうかを検出できます。次に、クラスを決定しCompilerGeneratedそのコンストラクターに反映して作成します。

決して単純な作業ではありませんが、LINQ to SQL、LINQ to XML などすべてがそれを行う方法です。

于 2009-03-03T12:25:07.943 に答える
1

これはコンパイルされますが、動作するかどうかはわかりません...

myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; });

p が変換対象であり、select ステートメントがラムダの関数宣言を使用して anon 型を返していると仮定します。

編集:これを動的に生成する方法もわかりません。しかし、少なくとも select ラムダを使用して複数の値を持つ anon 型を返す方法を示しています

編集2:

また、c# コンパイラが実際に anon 型の静的クラスを生成することも念頭に置いておく必要があります。したがって、anon 型は実際にはコンパイル後に型を持ちます。したがって、実行時にこれらのクエリを生成する場合 (そうであると思います)、さまざまなリフレクション メソッドを使用して型を構築する必要がある場合があります (それらを使用してオンザフライで型を作成できると思います)、作成された型を実行コンテキストにロードし、生成された出力でそれらを使用します。

于 2009-03-03T12:53:07.220 に答える
1

Slace が言ったように、Selectメソッドから返されるクラスが必要です。クラスを取得したら、System.Linq.Expressions.NewExpressionメソッドを使用して式を作成できます。

本当にこれを行いたい場合は、実行時にクラスを生成することもできます。LINQ 式ツリーを使用して実行できないため、もう少し作業が必要ですが、可能です。名前空間を使用System.Reflection.Emitしてそれを行うことができます-簡単な検索を行ったところ、これを説明する記事があります:

于 2009-03-04T13:40:40.593 に答える
0

次のように select ステートメントを動的に作成できる Dynamic Expression API を使用できます。

 Select("new(<property1>,<property2>,...)");

これを機能させるには、LINQ の Dynamics.cs ファイルと Visual Studio の言語サンプルが必要です。どちらもこのページの下部にリンクされています。同じ URL で、これが実際に行われていることを示す実際の例も見ることができます。

于 2009-03-03T12:23:56.400 に答える