表达式定义澄清

时间:2013-11-11 02:55:51

标签: c# asp.net-mvc generics expression extension-methods

任何人都可以提供一个明确的(易于理解)解释这里发生的事情(通常,泛型,扩展方法和表达式一起):

    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 是如何工作的(通常是他),我也知道这是扩展方法(以及它们如何工作),但是无法理解表达式然后在Html.TextBoxFor方法中处理(或处理等)(相对于Func的TModel和TProperty)。

提供的代码来自ASP.NET MVC,但这根本不是关于MVC:问题是仅相对于表达式

谢谢!

修改

经过一些更多的调查后,主要还有以下问题:TProperty类参数在提供的方法定义中的作用是什么?它如何影响方法调用?

2 个答案:

答案 0 :(得分:2)

您应该首先理解的是Expression<Func<...>>Func<...>基本相同。实际上,C#编译器可以隐式地将任何Func文字转换为等效的Expression

Func<TModel, TProperty表示&#34;一个委托(函数),它接受TModel个实例并返回TProperty值&#34; 。撇开整个表达树魔术,如果你写:

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只是上一个示例中名为函数f匿名版本。在C#中,这称为 Lambda Expression

你也可以这样写:

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

请注意,语法与以前完全相同,我们只是将其分配给Expression<Func<string, int>>而不是Func<string, int>。那么,你的问题基本上是Expression部分做了什么?

考虑它的最佳方式是:Func<...>是您可以运行的委托,并且已经编译。 {<1}}在编译之前是相同的委托。这是编译器看到的内容。上面的表达式是一个表示为:

的树
Expression<Func<...>>

这正式被称为Abstract Syntax Tree。 AST是在解析之后但在编译之前的代码。实际上,每个Lambda | +----> Member Access | | | +-----> Parameter (Name: "s", Type: System.String) | | | +-----> Member (Property: System.String.Length) | +----> Parameter (Name: "s", Type: System.String) 实际上都有一个Compile方法,您可以使用该方法将其编译为相应的可执行Expression类型。

使用Func代替Expression<...>的原因通常是,当您不想编译版本时,至少不会立即使用。{1}}。通常情况下,在这种情况下,你会使用一个,因为你只想获取属性名称(例如上面例子中的Func<...>),但你想这样做,同时保持编译时类型的所有好处安全,而不是使用反射,如果某些类别被更改,可能会神秘地破坏。

在上述特定情况下,您可以使用以下代码获取属性名称:

Length

当然,此时你可以做很多不同的事情,所以我不想尝试进一步细节 - 你可以深入研究MVC的源代码。此外,上面的示例不是生产就绪代码,因为它假设表达式是纯成员访问,但它可能更复杂,如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 } ,在这种情况下,上述方法将失败。顺便说一句,这样的表达式在Linq或MVC中使用时会神秘地失败。

希望能回答你的问题。委托是传递函数的一种方式,而表达式是传递代码的一种方式。除非您正在编写库或框架,否则通常不需要编写涉及s => s.Length + 1的代码。但是如果你 编写库或框架,那么表达式树是一个非常强大的工具,它比老式的反射更安全,可能更高效。

答案 1 :(得分:1)

类型Expression<Func<TModel, TProperty>>是&#34;一条指令,用于处理类型为TModel的传入对象,以返回TProperty&#34;类型的其他对象。在这里:

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等于&#34;只返回传入m&#34;的SomeValue属性。但它也可能是&#34;返回&#34; foo&#34;&#34;或&#34;返回null&#34;或者&#34;返回新的WeirdObject()&#34;。
方法TextBoxFor仅调用重载方法 的更新
首先,关于&#34;泛型类型的MSDN和谷歌文章&#34; (比如thisthis)可以更好地解释事情。使用此机制,您可以创建特定于实例创建时提供的类型的类或方法,以便它们能够返回相同类型的结果而不是常规&#34; object&#34;。
假设我想创建一个 List 类(在Microsoft执行此操作之前),它可以保存任何类型的值列表,并且还可以返回结果中的每一秒。我可以这样做:

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

您可以使用它来维护整数,字符串或性别或其他内容的列表,但 GetEvenItems 将始终返回您&#34;对象&#34;并且您需要将其恢复到原始类型以继续更具特色地工作。而不是这个我创建另一个类:

public class List<T>{ }

并且通过这种说法&#34;程序员必须指定所需的类型,以便它在课堂内始终是已知的,因此我可以随时为其投射值#34;正如我现在知道的类型,我可以使用它。例如:

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 ,这只是一个&#34;类型变量& #34 ;.你可以在声明中加上 TModel TMyThoughts 而不是 T
我也可以限制可能的类型。假设我的方法 DoTheWebJob 可以使用只能是 IController 类型的东西。然后我提供了一个额外的约束:

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

这意味着可以为我的班级指定唯一的 IController 后代。因为我的班级机构已经知道&#34; T IController 然后我可以轻松调用 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());
    }
}

非常酷,可以使用参数中提供的SPANname呈现value标记。所以我可以放任何东西,它会给我一个漂亮的SPAN,我甚至根本不需要在课堂宣言中使用泛型。
现在我想修改我的渲染器,以便将SPAN&#39; s ID属性设置为某个对象的属性名称。假设我的产品对象具有 Id 属性。我想将产品传递给渲染器,以便设置SPAN&#39; ID =&#34; Id&#34;内部html为 Id 的值(比如说5)。我的渲染器如何知道属性名称 ID ?如果我只是传递 Product.Id ,这只是一个整数值,渲染器将不知道这个属性名称是什么,并且无法设置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解析为&#34;获取对象&#34;的属性,但不是&#34; ;返回Id&#34;的值。从现在开始,身体&#34;认为&#34;这属于某个对象的属性,并且能够读取其名称和值。使用这样的表达式,我们可以使渲染器知道我们的属性名称,以便它可以呈现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
}

这意味着如果 htmlHelper 是从HtmlHelper<MyModel>类型创建的,那么 RenderSpan 将需要Expression<Func<MyModel, object>>表达式。例如:myModel => myModel.Id;。在cshtml文件中,您尝试创建 TextBoxFor ,并且会看到它需要与@model相同的类型。这是因为实际的html帮助器被隐式创建为new HtmlHelper<MyModel>()。现在,当 RenderSpan 知道 T HtmlHelper 的类型时,它可以让你使用 T x => x.Id右侧的>属性。它知道 T = 实体,您可以说&#34; x.Id &#34;。如果表达式是object, object那么你就无法做到这一点。在Microsoft的声明中,他们使用 TModel 而不是 T 来让您直观地了解它的含义。
好的,现在简而言之:
1.你在cshtml中写@model MyModel
2. MVC创建一个HtmlHelper<MyModel>助手
3.由于#2 SpanForTextBoxFor知道表达式传入参数是 MyModel 类型,因此可以使用其属性进行操作并允许您使用右键输入表达的一部分
我不确定他们为什么需要第二个类型参数 TProperty ,它可能只是对象。可能它在 TextBoxFor 方法中更深入地传播。