1

ここで何が起こるかについて明確な (そして理解しやすい) 説明を誰かが提供できますか (一般に、ジェネリック、拡張メソッド、式をすべてまとめて):

    public static MvcHtmlString TextBoxFor<TModel, TProperty>
        (this HtmlHelper<TModel> htmlHelper, 
              Expression<Func<TModel, TProperty>> expression)
    {
        return htmlHelper.TextBoxFor(expression, format: null);
    }

さらにここでそれを使用する:

    Html.TextBoxFor(o => Model.SomeValue)

理解する上で最も問題となるのは、この条件で Expression パラメータがどのように機能するかということです。私はGenericsがどのように機能するか(一般的には he he) を知っており、これが拡張メソッドであること(およびそれらがどのように機能するか) も知っていますが、Html.TextBoxFor メソッドで式がどのように処理 (または処理など) されたかをキャッチできません(相対的にFunc の TModel および TProperty)。

提供されているコードは ASP.NET MVC からのものですが、それは MVC に関するものではありません。質問はExpressions に対してのみです。

ありがとうございました!

編集:

さらに調査を重ねた結果、次のような疑問が残りました:TProperty提供されたメソッド定義におけるクラス引数の役割は何ですか? また、それはメソッド呼び出しにどのように影響しますか?

4

3 に答える 3

2

最初に理解しておくべきことは、Expression<Func<...>>Func<...>は本質的に同じものであるということです。実際、C# コンパイラは、任意Funcのリテラルを同等の に暗黙的に変換できExpressionます。

AFunc<TModel, TPropertyは、「インスタンスを受け取り、を返すデリゲート (関数)」をTModelTProperty意味します。式ツリーの魔法全体を脇に置いて、次のように記述します。

Func<string, int> func = s => s.Length;

それは書くことと同じです:

Func<string, int> func = delegate(string s) { return s.Length; };

これは、次のように書くことも同じです。

int f(string s)
{
    return s.Length;
}

Func<string, int> func = f;

つまり、最初のバージョンは、最後の例の名前付き関数のs => s.Length単なる匿名バージョンです。C# では、これはラムダ式と呼ばれます。f

次のように書くこともできます。

Expression<Func<string, int>> expr = s => s.Length;

構文は前とまったく同じExpression<Func<string, int>>であることに注意してくださいFunc<string, int>。それで、あなたの質問は、本質的に、Expression部品は何をするのですか?

これを考える最良の方法Func<...>は、実行可能なデリゲートであり、既にコンパイルされているということです。コンパイルExpression<Func<...>>と同じデリゲートです。それはコンパイラが見るものです。上記の式は、次のように表されるツリーです。

Lambda
   |
   +----> Member Access
   |        |
   |        +-----> Parameter (Name: "s", Type: System.String)
   |        |
   |        +-----> Member (Property: System.String.Length)
   |
   +----> Parameter (Name: "s", Type: System.String)

これは、正式には抽​​象構文ツリーと呼ばれるものです。AST は、解析後、コンパイル前のコードの外観です。実際、すべてExpressionには、Compile対応する実行可能Funcタイプにコンパイルするために使用できるメソッドがあります。

Expression<...>a の代わりにan を使用する理由Func<...>は、一般に、コンパイルされたバージョンが必要ない場合少なくともすぐには必要ない場合です。非常に多くの場合、このような場合、プロパティ名を取得したいだけで (Length上記の例のように)、コンパイル時の型の安全性のすべての利点を維持しながら取得したいため、1 つを使用します。一部のクラスが変更されると不思議なことに壊れる可能性のあるリフレクションを使用します。

上記の特定のケースでは、次のコードを使用してプロパティ名を取得できます。

void Foo(Expression<Func<T, TResult>> expr)
{
    var member = ((MemberExpression)expr.Body).Member;
    var memberName = member.Name;
    // Do something with the member and/or name
}

もちろん、この時点でさまざまなことを行うことができるので、これ以上詳しく説明するつもりはありません。そのためには、MVC ソース コードを掘り下げることができます。また、上記の例は、式が純粋なメンバー アクセスであることを前提としているため、プロダクション対応のコードではありs => s.Length + 1ません。ところで、このような式は、Linq や MVC で使用されている場合にも不思議なことに失敗する可能性があります。

うまくいけば、それはあなたの質問に答えます。デリゲートは関数を渡す方法ですが、式はコードを渡す方法です。ライブラリやフレームワークを作成している場合を除き、通常はコードを記述する必要はありません。Expressionしかし、ライブラリやフレームワークを作成している場合、式ツリーは非常に強力なツールであり、昔ながらのリフレクションよりもはるかに安全であり、パフォーマンスも大幅に向上する可能性があります。

于 2013-11-11T04:02:44.037 に答える
1

タイプExpression<Func<TModel, TProperty>>は、「タイプ TModel の着信オブジェクトを処理して、タイプ TProperty の他のオブジェクトを返す命令」です。ここ:

public static MvcHtmlString TextBoxFor<TModel, TProperty>
    (this HtmlHelper<TModel> htmlHelper, 
          Expression<Func<TModel, TProperty>> expression)
{
    return htmlHelper.TextBoxFor(expression, format: null);
}  

HtmlHelper の TextBoxFor 拡張メソッドには、上記のようなパラメーターが必要ですが、式の引数は、HtmlHelper が作成されたのと同じ型、つまり TModel であり、TProperty 型のオブジェクトを返す必要があります。
実際のパラメーターm => m.SomeValueは、「受信した m の SomeValue プロパティを返すだけ」と同じですが、「"foo" を返す」または「null を返す」または「新しい WeirdObject() を返す」の場合もあります。
メソッドTextBoxForはオーバーロード メソッドのみを呼び出します。
更新
まず、「ジェネリック型」に関する MSDN と Google の記事 ( thisthisのように)) 私よりもはるかにうまく説明できます。このメカニズムを使用して、インスタンスの作成時に提供された型に固有のクラスまたはメソッドを作成し、たとえば、一般的な「オブジェクト」ではなく同じ型の結果を返すことができるようにします。任意の型の値のリストを保持でき、結果としてそれらのすべての秒を返すことができるList
クラスを (Microsoft が行う前に) 作成したいとします。私はこのようにすることができます:

public class List{
    public IEnumerable Items; // collection of "objects"
    public IEnumerable GetEvenItems(){
    // some implementation returning another "objects" collection
    }
}

int、文字列、性別などのリストを維持するために使用できますが、GetEvenItemsは常に「オブジェクト」を返すため、より具体的に作業を続けるには、それらを元の型にキャストする必要があります。これの代わりに私は別のクラスを作ります:

public class List<T>{ }

そして、これにより、「プログラマーは目的の型を指定する必要があるため、クラス内で常に認識されるため、いつでも値をキャストできます」。タイプがわかったので、使えます。例えば:

public class List<T>{
    public T[] Items; // collection of strongly typed values
    public T[] GetEvenItems(){
        // some implementation returning typed collection
    }
}

これにより、アイテムは作成時に提供された特定のタイプのものになると言えます。また、私の Item とGetEvenItemsは特定のタイプのアイテムも返すので、整数または文字列のコレクションとしてそれらを操作できます。外部コードが myList.GetEvenItems を呼び出すと、メソッドが T の配列を返すことがわかります。ちなみに、Tの代わりに他の名前を使用できます。これは単なる「型変数」です。Tの代わりにTModelまたはTMyThoughtsを宣言に入れることができます。 可能なタイプを制限することもできます。私のメソッドDoTheWebJobは、 IControllerのみで動作できるとします。
タイプ。次に、追加の制約を提供します。

public class MyClass<T> where T: IController
{
    public T[] Items;
    public void DoTheWebJob()
    {
        Items[0].Execute(null);
    }
}

つまり、クラスに指定できるのはIControllerの子孫のみです。私のクラス本体はTIControllerであることを既に「知っている」ので、IController固有のメソッドを簡単に呼び出すことができます。
また、プログラマーが次のように複数のタイプを提供する必要があるように、クラスまたはメソッドを設計することもできます。

public class List<T1, T2>{ }

ここまでは順調ですね。行きましょう

System.Web.Mvc.HtmlHelper<T>

これはちょうど私たちのようなものList<T>です.: インスタンスを作成するとき、プログラマーは実際のT値を次のように指定します:

HtmlHelper<int> myHelper = new HtmlHelper<int>();

HTMLタグをレンダリングする独自のヘルパーが欲しいとしましょう。

public class MyHtmlHelper<T>
{
    public string RenderSpan(string name, object value)
    {
        return String.Format("<span id=\"{0}\">{1}</span>", name, value.ToString());
    }
}

SPANこれはクールで、params を使用してタグをレンダリングし、paramsnameで提供できます。valueだから私は何でも入れることができ、見栄えがよくなりますSPAN。クラス宣言にジェネリックはまったく必要ありません。
ここで、レンダラーを変更して、SPANID属性をオブジェクトのプロパティの名前として設定します。Idプロパティを持つProductオブジェクトがあるとします。Productをレンダラーに渡したいので、のID ="Id" と内部の html をIdの値 (たとえば 5) として設定します。レンダラーはどのようにしてプロパティIdの名前を知ることができますか? 単純にProduct.Idを渡すとSPAN、これは単なる整数値になり、レンダラーはこのプロパティ名が何であるかわからず、設定できませんSPAN ID=...
まあ、式の力が私たちを助けます. まずFunc<T1, T2>、T1 型のパラメーターを受け取り、T2 型の結果を返すデリゲートです。Expression<Func<T1, T2>>デリゲートのロジックを記述する式ですFunc<T1, T2>。したがって、デリゲート自体に簡単にコンパイルできますが、逆方向にはコンパイルできません。

次のコードを記述します。

internal class Program
{
    public class Entity
    {
        public int Id { get; set; }
    }
    private static void Main(string[] args)
    {
        Expression<Func<Entity, int>> fn = e => e.Id;
        // breakpoint here
    }
}

ブレークポイントを設定し、fn.Body データ型を監視します。これはPropertyExpressionになります。つまり、システムは「オブジェクトのe => e.Idこのプロパティを取得する」と解析しますが、「Id の値を返す」とは解析しません。これ以降、ボディはこれを何らかのオブジェクトのプロパティとして「考え」、その名前と値の両方を読み取ることができます。このような式を使用して、レンダラーにプロパティ名を認識させ、レンダリングできるようにしますSPAN

public class MyHtmlHelper<T>
{
    public string RenderSpan(string name, object value)
    {
        return String.Format("<span name=\"{0}\">{1}</span>", name, value.ToString());
    }
    public string RenderSpan(System.Linq.Expressions.PropertyExpression pe)
    {
        // extract property name and value and render SPAN here

    }
    public string RenderSpan(Expression<Func<object, object>> expr)
    {
        // if specified expr was like x => x.Id then it will actually be parsed like PropertyExpression in above
    }
}

ただし、クラス宣言には既にエンティティ型Tがあり、この型を使用できます。したがって、最後のメソッドを次のように変更できます。

public string RenderSpan(Expression<Func<T, object>> expr)
{
    // if specified expr was like x => x.Id then it will actually be parsed like PropertyExpression in above
}

つまり、htmlHelperHtmlHelper<MyModel>がタイプとして作成された場合、 RenderSpanには式が必要になりExpression<Func<MyModel, object>>ます。例: myModel => myModel.Id;。cshtml ファイルでTextBoxForを作成しようとすると、以前と同じタイプが必要であることがわかります@model。これは、実際の html ヘルパーが暗黙的に として作成されたためnew HtmlHelper<MyModel>()です。ここで、RenderSpanTがHtmlHelperの作成に使用された型であることを認識すると、 の右側の部分でTのプロパティを使用できるようになりますx => x.IdT =エンティティを認識しており、 「 x.Id 」と言うことができます。もし表現がobject, objectその後、これを行うことはできません。Microsoft の宣言では、 Tの代わりにTModelを使用して、それが何であるかを直感的に理解できるようにしています。 さて、簡単に言うと: 1. cshtml に書き込みます 2. MVC はヘルパーを作成します 3. #2 は式の入力引数がMyModel型であることを認識しているため、そのプロパティで操作でき、入力したものを使用できます式の右側の部分TProperty として 2 番目の型パラメーターが必要な理由がわかりません。それは単にobjectである可能性があります。おそらくそれはTextBoxForメソッドでより深く伝播されます。

@model MyModel
HtmlHelper<MyModel>
SpanForTextBoxFor

于 2013-11-11T03:21:23.167 に答える