任何人都可以提供一个明确的(易于理解)解释这里发生的事情(通常,泛型,扩展方法和表达式一起):
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
类参数在提供的方法定义中的作用是什么?它如何影响方法调用?
答案 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; (比如this和this)可以更好地解释事情。使用此机制,您可以创建特定于实例创建时提供的类型的类或方法,以便它们能够返回相同类型的结果而不是常规&#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());
}
}
非常酷,可以使用参数中提供的SPAN
和name
呈现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 SpanFor
或TextBoxFor
知道表达式传入参数是 MyModel 类型,因此可以使用其属性进行操作并允许您使用右键输入表达的一部分
我不确定他们为什么需要第二个类型参数 TProperty ,它可能只是对象。可能它在 TextBoxFor 方法中更深入地传播。