所以,边教边学。这就是我们的生活。呵呵
这个问题最初似乎澄清了一些我对发布的代码不清楚的具体事情,但经过所有调查,我想,分享我对泛型、扩展方法和表达式的全部理解是有道理的(如果他们一起使用)。
这很“吓人”,但从其他人的手来看,C# 也很漂亮,当这样一个简单的方法调用时:
Html.TextBoxFor(model => model.SomeValue)
在自身内部隐藏了这样的范围和“深度”声明,例如:
public static class InputExtensions
{
public static MvcHtmlString TextBoxFor<TModel, TProperty>
(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
string format = (string) null;
return InputExtensions
.TextBoxFor<TModel, TProperty>(htmlHelper, expression, format);
}
}
提供的代码是使用 Resharper 的“Go to Implementation”收到的反编译版本。(实际上,升级到 Resharper 8 后我必须使用“导航到/反编译的源代码”,但这不是在这里讨论的地方——以防万一)。
因此,我将尝试解释此类方法定义的整个一般“剖析”(至少,我对此有所了解)。
目标:
拥有一个扩展方法,该方法将扩展某些泛型类并允许操作该类数据(在编译时:具有 Intellisense 和重构的所有好处),具体取决于已将哪个类作为类型参数传递给提到的泛型类。
在现实生活中的解释中,我将描述如下:我们有一个类Car
(通用类),它(通常)“适合”携带不同的东西,如牛奶、卷心菜、自行车。但是当我们定义类似的东西时Car<Milk>
,那辆车只能运载牛奶。此外,我们认为该类构建已经完成(我们无法更改它)。但是,我们还想购买一些拖车,它会携带与已经为某些汽车定义的产品完全相同的产品,例如具有“CreateATrailer”方法。我们能够确定我们需要的拖车类型的方法是使用产品(Milk
在我们的例子中)测量单位(因为不同产品的单位不同:窝、公斤、物品)。在这种情况下,通用扩展方法(可能使用表达式)非常方便。(这可能不是一个理想的现实生活示例,但这就是我想到的)。
简而言之:
public static class InputExtensions
// ^^ this is where Extension Methods must be placed (inside of a static class)
{
public static MvcHtmlString TextBoxFor<TModel, TProperty>
// ^^ they must also be static ^^ here must be defined all the generic types
// which are involved withing the method
(this HtmlHelper<TModel> htmlHelper,
// ^^ the first parameter must have "this"
// this is a parameter which defines the type that the method operates on
// so, in this case, it must be some "HtmlHelper<TModel>" class instance
Expression<Func<TModel, TProperty>> expression)
// ^^ the second parameter in the declaration,
// but the first one which appears from caller side (the only one in this very case)
{
string format = (string) null;
return InputExtensions.TextBoxFor<TModel, TProperty>
(htmlHelper, expression, format);
// or might also be (dependently if the types
// can be resolved automatically by compiller (the explanation below))
// as follows:
return InputExtensions.TextBoxFor
(htmlHelper, expression, format);
}
}
深潜:
我不会重复对泛型、扩展方法和表达式(实际上,最初它被称为“表达式树”)的所有详细解释,它们可以通过 google 和 msdn 广泛获得。但将更多地关注这一切如何协同工作。
public static class InputExtensions
没有什么具体的。只是任何静态类。它的名字大多不起任何作用。
public static MvcHtmlString TextBoxFor<TModel, TProperty>
该方法必须是静态的。
如果我们不使用泛型,我们将完全省略 <...> 部分。但是如果我们这样做,我们应该在那里指定所有类型(类型模板),哪些运行时类型将由编译器自动解析(取决于您在调用方法时传递的参数),或者必须明确定义,例如someObject.OurExtensionMethod<string, int>
.
(this HtmlHelper<TModel> htmlHelper,
“this”修饰符必须放在第一个参数旁边,这实际上是该方法是扩展的标志。
htmlHelper
参数将表示对象,我们在其上调用扩展方法。为了更容易理解,它可以简单地替换为“@this”名称,如(this HtmlHelper<TModel> @this
. 唯一的区别是您显然无权访问该类的任何私有成员(与要扩展的类的“内部”相反)。
这只是一个常见的泛型类型原型——没有什么特别的。它可以是我们想要的任何东西。甚至string
,即(this string @this,
。
Expression<Func<TModel, TProperty>> expression)
对于我来说,这是最棘手的部分。
因此,如果说到扩展方法部分,这将是您在扩展某些类时需要在其调用时提供给方法的第一个参数。
至于表达式......我们在这里使用它来允许用户传递一些值,这些值可以进一步(通过内部方法)从我们提供给他的实例中获取。即,Func<TModel, TProperty>
(通常与Expression<Func<TModel, TProperty>>
(但通常不完全相同)相同)意味着,在方法调用上,我们为用户提供了一些使用类型的能力,这是用于我们的 HtmlHelper 实例化的类型参数.
换句话说,如果我们创建了一个Car<Milk>
(在我们的例子中是Milk
指TModel
) 的实例,那么我们将向调用者提供 Milk 类型Html.TextBoxFor(ourKindOfMilkObject => ourKindOfMilkObject.MeasureUnits)
,例如 。
(我想,对于不太熟悉表达式(甚至是 Func/Action 概念)的人来说,这可能很难理解,所以我只是希望如果您阅读本文,您已经知道它是什么(至少,基本上) )。
这里最棘手的问题是,TProperty
为什么这里甚至需要它。
好的,它是如何工作的:
关于方法调用,再次。如果我们的对象如下(例如):
var html = new HtmlHelper<CustomModel>();
var car = new Car<Milk>();
那么我们必须这样调用我们的扩展方法:
html.TextBoxFor<CustomModel, string>(model => model.SomeValue);
car.AddATrailer<Milk,ParticularMeasureUnitsType>
(theCarProduct => theCarProduct.MeasureUnits);
但是如果所有 Generic Types Templates 运行时类型都被解析(在我们的例子中是这样(因为Car
知道Milk
由它的定义创建(new Car<Milk>()
)并且表达式Func
返回值类型也可以从SomeValue
类型定义中获得),那么我们简化:
html.TextBoxFor(model => model.SomeValue);
car.AddATrailer(theCarProduct => theCarProduct.MeasureUnits);
和:
关于泛型非常重要的一点是,我们可以使用关键字来定义哪些类/接口可用于泛型类型模板的限制where
,例如:
(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
where TModel : CustomModel where some : ParticularValueType
否则,如果我们使用object
而不是 TProperty,我们将无法控制我们可以传递和不能传递给方法的内容(同样的方法,允许调用扩展方法和不允许调用的方法)。
我相信,这里可能还有一些需要改进或纠正的地方,所以,请对此发表评论——我很乐意修改这个话题。