6

次の式があるとします。

Expression<Action<T, StringBuilder>> expr1 = (t, sb) => sb.Append(t.Name);
Expression<Action<T, StringBuilder>> expr2 = (t, sb) => sb.Append(", ");
Expression<Action<T, StringBuilder>> expr3 = (t, sb) => sb.Append(t.Description);

これらを次と同等のメソッド/デリゲートにコンパイルできるようにしたいと思います。

void Method(T t, StringBuilder sb) 
{
    sb.Append(t.Name);
    sb.Append(", ");
    sb.Append(t.Description);
}

これにアプローチする最良の方法は何ですか?理想的には、上記の方法と同等のパフォーマンスで、うまく機能することを望みます。

更新 C#3でこれを直接行う方法はないようですが、System.Reflection.Emitで使用できるように式をILに変換する方法はありますか?

4

5 に答える 5

4

残念ながら、.NET 3.5 では、一連の任意の操作を実行する式を作成することはできません。サポートされている式のリストは次のとおりです。

  • 算術演算: Add、AddChecked、Divide、Modulo、Multiply、MultiplyChecked、Negate、NegateChecked、Power、Subtract、SubtractChecked、UnaryPlus
  • 作成: Bind、ElementInit、ListBind、ListInit、MemberBind、MemberInit、New、NewArrayBounds、NewArrayInit
  • ビット単位: And、ExclusiveOr、LeftShift (<<)、Not、Or、RightShift (>>)
  • 論理: AndAlso (&&)、条件 (? :)、Equal、GreaterThan、GreaterThanOrEqual、LessThan、* LessThanOrEqual、NotEqual、OrElse (||)、TypeIs
  • メンバー アクセス: ArrayIndex、ArrayLength、Call、Field、Property、PropertyOrField
  • その他: Convert、ConvertChecked、Coalesce (??)、Constant、Invoke、Lambda、Parameter、TypeAs、Quote

.NET 4 は、次の式を追加してこの API を拡張します。

  • 変異: AddAssign、AddAssignChecked、AndAssign、Assign、DivideAssign、ExclusiveOrAssign、LeftShiftAssign、ModuloAssign、MultiplyAssign、MultiplyAssignChecked、OrAssign、PostDecrementAssign、PostIncrementAssign、PowerAssign、PreDecrementAssign、PreIncrementAssign、RightShiftAssign、SubtractAssign、SubtractAssignChecked
  • 算術演算: デクリメント、デフォルト、インクリメント、OnesComplement
  • メンバー アクセス: ArrayAccess、動的
  • 論理: ReferenceEqual、ReferenceNotEqual、TypeEqual
  • フロー: ブロック、ブレーク、続行、空、後藤、IfThen、IfThenElse、IfFalse、IfTrue、ラベル、ループ、リターン、スイッチ、SwitchCase、アンボックス、変数
  • 例外: キャッチ、リスロー、スロー
  • デバッグ: ClearDebugInfo、DebugInfo

ブロック式は特に興味深いです。

于 2010-03-14T13:37:27.290 に答える
1

できますが、簡単な作業ではありません。

Expression 型の変数がある場合、その Body プロパティを調べて、式のデータ構造を見つけることができます。

必要な結果が得られないため、コンパイラにコンパイルを依頼することはできません。すべての式の本体を解析し、何らかの方法でそれらを 1 つのメソッドに結合する必要があります。すべて同時に IL を発行することによって (または、C# を生成し、IL が一歩遠すぎると感じた場合はそれをコンパイルすることによって)。

LINQ-to-SQL が式を SQL クエリにコンパイルするのと同じように、式を必要なものにコンパイルできます。多くの作業が待ち受けていますが、サポートしたいものを実装するだけで済みます。

このかなり些細なケースでは、独自の LINQ プロバイダーを作成する必要はないと思います。渡された式をそのまま使用して、そこから移動することができます。しかし、あなたのアプリケーションはそれよりも少し複雑だと思います。

于 2010-03-18T11:53:05.660 に答える
1

4.0 では、ツリーでブロック操作がサポートされているため、これははるかに簡単です (ただし、C# 式コンパイラではサポートされていません)。

StringBuilderただし、 「流れるような」API を公開するという事実を利用することで、これを行うことができます。その代わりに、以下のようAction<T,StringBuilder>Func<T,StringBuilder,StringBuilder>- があります (この場合、これらの式を表現するための実際の構文は同じであることに注意してください):

class Program
{
    static void Main()
    {
        Foo(new MyType { Name = "abc", Description = "def" });
    }
    static void Foo<T>(T val) where T : IMyType
    {
        var expressions = new Expression<Func<T, StringBuilder, StringBuilder>>[] {
                (t, sb) => sb.Append(t.Name),
                (t, sb) => sb.Append(", "),
                (t, sb) => sb.Append(t.Description)
        };
        var tparam = Expression.Parameter(typeof(T), "t");
        var sbparam = Expression.Parameter(typeof(StringBuilder), "sb");

        Expression body = sbparam;
        for (int i = 0; i < expressions.Length; i++)
        {
            body = Expression.Invoke(expressions[i], tparam, body);
        }
        var func = Expression.Lambda<Func<T, StringBuilder, StringBuilder>>(
            body, tparam, sbparam).Compile();

        // now test it
        StringBuilder sbInst = new StringBuilder();
        func(val, sbInst);
        Console.WriteLine(sbInst.ToString());
    }
}
public class MyType : IMyType
{
    public string Name { get; set; }
    public string Description { get; set; }
}
interface IMyType
{
    string Name { get; }
    string Description { get; }
}

ツリーを調べて IL を手動で発行することは確かに可能DynamicMethodですが (おそらく)、複雑さを制限することについていくつかの決定を下す必要があります。提示されたコードについては、妥当な時間で実行できますが(まだ簡単ではありません)、より複雑なものを期待する場合Expressionは、揚げ物になります。

于 2010-03-20T08:43:54.790 に答える
0

あなたは.NET 4でのみそれを行うことができます.申し訳ありませんが詳細を知りません.

編集:

Reflection.Emit に慣れている場合は、これらの式を順番に呼び出すメソッドを発行できます。

別の方法:

「do」メソッドを作成します。つまり、次のようにします。

void Do(params Action[] actions)
{
  foreach (var a in actions) a();
}
于 2010-03-10T17:32:06.000 に答える
0

この問題を調べるもう 1 つの方法は、デリゲートがマルチキャストであることを思い出すことです。Action何度でも組み合わせることができます。

class Program
{
    static void Main()
    {
        Foo(new MyType { Name = "abc", Description = "def" });
    }

    static void Foo<T>(T val) where T : IMyType {
        var expressions = new Expression<Action<T, StringBuilder>>[] {
                (t, sb) => sb.Append(t.Name),
                (t, sb) => sb.Append(", "),
                (t, sb) => sb.Append(t.Description)
        };
        Action<T, StringBuilder> result = null;
        foreach (var expr in expressions) result += expr.Compile();
        if (result == null) result = delegate { };
        // now test it
        StringBuilder sbInst = new StringBuilder();
        result(val, sbInst);
        Console.WriteLine(sbInst.ToString());
    }
}
public class MyType : IMyType
{
    public string Name { get; set; }
    public string Description { get; set; }
}
interface IMyType
{
    string Name { get; }
    string Description { get; }

}
于 2010-03-20T09:18:10.480 に答える