3

次のクラスがあるとします。

public class MyClass {

    private readonly UrlHelper _urlHelper;

    // constructor left out for brevity

    // this is one of many overloaded methods
    public ILinkableAction ForController<TController, T1, T2>(Expression<Func<TController, Func<T1, T2>>> expression) {
        return ForControllerImplementation(expression);
    }

    private ILinkableAction ForControllerImplementation<TController, TDelegate>(Expression<Func<TController, TDelegate>> expression) {
        var linkableMethod = new LinkableAction(_urlHelper);

        var method = ((MethodCallExpression) expression.Body).Method;
        method.GetParameters().ToList().ForEach(p => linkableMethod.parameters.Add(new Parameter {
            name = p.Name,
            parameterInfo = p
        }));

        return linkableMethod;
    }
}

および次の実装:

var myClass = new MyClass(urlHelper);
myClass.ForController<EventsController, int, IEnumerable<EventDto>>(c => c.GetEventsById);

どこGetEventsByIdに署名があります:

IEnumerable<EventDto> GetEventsById(int id);

エラーが発生します:

タイプ 'System.Linq.Expressions.UnaryExpression' のオブジェクトをタイプ 'System.Linq.Expressions.MethodCallExpression' にキャストできません。

  1. 式を適切な型に変換して、指定された式を取得するにはどうすればよいMethodInfoですか?
  2. TDelegate、上記の例ではFunc<int, IEnumerable<EventDto>>、実行時です。では、式からDelegateを取得できないのはなぜMethodInfoですか?
4

2 に答える 2

8

問題は、 aMethodCallExpressionが実際にメソッドでなければならないことです。検討:

public static void Main()
{
    Express(str => str.Length);
    Console.ReadLine();
}


static void Express(Expression<Func<String, Int32>> expression)
{
    // Outputs: PropertyExpression (Which is a form of member expression)
    Console.WriteLine(expression.Body.GetType()); 
    Console.ReadLine();
}

式はコンパイル時に決定されます。つまり、プロパティstr => str.Length呼び出していると言うと、コンパイラはこれを に解決します。strMemberExpression

代わりに、ラムダを次のように変更すると:

Express(str => str.Count());

次に、コンパイラは私が呼び出しCount()ていることを理解し、実際にはメソッドであるためstr、 ... に解決されます。MethodCallExpression

Stringただし、これは、 aを an に「変換」できるのと同様に、式をある型から別の型に実際に「変換」できないことを意味することに注意してくださいInt32。解析はできますが、それは実際には会話ではないことがわかると思います...

...とはいえ、MethodCallExpression何もないところから a を構築できるため、場合によっては役立ちます。たとえば、ラムダを構築しましょう。

(str, startsWith) => str.StartsWith(startsWith)


(1) まず、2 つのパラメーターを作成することから始める必要があります。(str, startsWith) => ...

// The first parameter is type "String", and well call it "str"
// The second parameter also type "String", and well call it "startsWith"
ParameterExpression str = Expression.Parameter(typeof(String), "str");
ParameterExpression startsWith = Expression.Parameter(typeof(String), "startsWith");


(2) 次に、右側で以下を構築する必要がありますstr.StartsWith(startsWith)。まず、リフレクションを使用して、次のように type の単一の入力を受け取る のStartsWith(...)メソッドにバインドする必要があります。StringString

// Get the method metadata for "StartsWith" -- the version that takes a single "String" input.
MethodInfo startsWithMethod = typeof(String).GetMethod("StartsWith", new [] { typeof(String) });


(3) binding-metadata を取得したMethodCallExpressionので、次のように a を使用して実際にメソッドを呼び出すことができます。

//This is the same as (...) => str.StartsWith(startsWith);
// That is: Call the method pointed to by "startsWithMethod" bound above. Make sure to call it
// on 'str', and then use 'startsWith' (defined above as well) as the input.
MethodCallExpression callStartsWith = Expression.Call(str, startsWithMethod, new Expression[] { startsWith });


(4) これで、左辺(str, startsWith)/ と右辺ができstr.StartsWith(startsWith)ました。次に、それらを 1 つのラムダに結合する必要があります。最終的なコード:

// The first parameter is type "String", and well call it "str"
// The second parameter also type "String", and well call it "startsWith"
ParameterExpression str = Expression.Parameter(typeof(String), "str");
ParameterExpression startsWith = Expression.Parameter(typeof(String), "startsWith");

// Get the method metadata for "StartsWith" -- the version that takes a single "String" input.
MethodInfo startsWithMethod = typeof(String).GetMethod("StartsWith", new[] { typeof(String) });

// This is the same as (...) => str.StartsWith(startsWith);
// That is: Call the method pointed to by "startsWithMethod" bound above. Make sure to call it
// on 'str', and then use 'startsWith' (defined above as well) as the input.
MethodCallExpression callStartsWith = Expression.Call(str, startsWithMethod, new Expression[] { startsWith });

// This means, convert the "callStartsWith" lambda-expression (with two Parameters: 'str' and 'startsWith', into an expression
// of type Expression<Func<String, String, Boolean>
Expression<Func<String, String, Boolean>> finalExpression =
    Expression.Lambda<Func<String, String,  Boolean>>(callStartsWith, new ParameterExpression[] { str, startsWith });

// Now let's compile it for extra speed!
Func<String, String, Boolean> compiledExpression = finalExpression.Compile();

// Let's try it out on "The quick brown fox" (str) and "The quick" (startsWith)
Console.WriteLine(compiledExpression("The quick brown fox", "The quick")); // Outputs: "True"
Console.WriteLine(compiledExpression("The quick brown fox", "A quick")); // Outputs: "False"

更新 さて、おそらく次のようなものがうまくいくかもしれません:

class Program
{
        public void DoAction()
        {
            Console.WriteLine("actioned");
        }

        public delegate void ActionDoer();

        public void Do()
        {
            Console.ReadLine();
        }

        public static void Express(Expression<Func<Program, ActionDoer>> expression)
        {
            Program program = new Program();
            Func<Program, ActionDoer> function = expression.Compile();
            function(program).Invoke();
        }

        [STAThread]
        public static void Main()
        {
            Express(program => program.DoAction);
            Console.ReadLine();
        }
}

更新:偶然何かに出くわしました。次のコードを検討してください。

    public static String SetPropertyChanged<T>(Expression<Func<T, Object>> expression)
    {
        UnaryExpression convertExpression = (UnaryExpression)expression.Body;
        MemberExpression memberExpression = (MemberExpression)convertExpression.Operand;
        return memberExpression.Member.Name;

        ...
    }

入力は、WPF の単純なラムダです。

base.SetPropertyChanged(x => x.Visibility);

に投影しているObjectので、ビジュアルスタジオがこれを に変換することに気付きましたUnaryExpression。これは、あなたが遭遇しているのと同じ問題だと思います。ブレークポイントを置いて実際の式を調べると (私の場合)、 と表示されますx => Convert(x.Visibility)。問題はConvert(事実上、現在不明な型への単なるキャストです) です。あなたがしなければならないのは、それを削除することだけです(上記のコードでOperandメンバーを使用して行っているように、すべて設定されているはずですMethodCallExpression.

于 2013-05-30T13:28:47.440 に答える