所以,边教边学。这就是我们的生活。呵呵
这个问题最初似乎澄清了我对发布的代码不清楚的一些具体事情,但经过所有调查,我想,分享我对泛型、扩展方法和表达式的全部理解是有道理的(如果它们一起使用)。
这很“吓人”,但从其他人的手来看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 后我必须使用“Navigate To/Decompiled Sources”,不过这里不讨论——以防万一)。
因此,我将尝试解释此类方法定义的整个一般“解剖”(至少,我对此有所了解)。
目标:
拥有一个扩展方法,该方法可以扩展某些泛型类并允许操作该类数据(在编译时:具有 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);
}
}
深潜:
我不会重复generics、extension methods 和expressions (actually, initially it's called "expressions trees") 的所有详细说明,这些说明可通过 google 和 msdn 广泛获得。但将更多地关注这一切如何协同工作。
public static class InputExtensions
没有什么具体的。只是任何静态类。它的名字大多不起任何作用。
public static MvcHtmlString TextBoxFor<TModel, TProperty>
方法必须是静态的。
如果我们不使用泛型,我们将完全省略 <...> 部分。但是如果我们这样做,我们应该在那里指定所有类型(类型模板),哪些运行时类型将由编译器自动解析(取决于您在调用方法时传递的参数),或者必须明确定义,如@987654332 @。
(this HtmlHelper<TModel> htmlHelper,
“this”修饰符必须放在第一个参数旁边,this is实际上是方法是扩展的标志。
htmlHelper 参数将表示对象,我们在其上调用扩展方法。为了更容易理解,可以简单地将其替换为“@this”名称,例如(this HtmlHelper<TModel> @this。唯一的区别是您显然无权访问该类的任何私有成员(与要扩展的类的“内部”相反)。
这里只是一个通用的泛型类型原型——没有什么特别的。它可以是我们想要的任何东西。甚至string,即(this string @this,。
Expression<Func<TModel, TProperty>> expression)
这对我来说是最棘手的部分。
所以,如果说到扩展方法部分,这将是您在扩展某些类时需要提供给方法调用的第一个参数。
至于表达式......我们在这里使用它来允许用户传递一些值,这些值可以进一步(通过内部方法)从我们提供给他的实例中获取。即,Func<TModel, TProperty>(通常与Expression<Func<TModel, TProperty>> 相同(但通常不完全相同))意味着,在方法调用中,我们为用户提供了一些使用该类型的能力,该类型用于我们的HtmlHelper 实例化为 Type 参数。
换句话说,如果我们创建了一个Car<Milk> 的实例(在我们的例子中Milk 表示TModel),那么我们将向调用者提供Milk 类型,例如Html.TextBoxFor(ourKindOfMilkObject => ourKindOfMilkObject.MeasureUnits)。
(我想,对于不太熟悉表达式(甚至是 Func/Action 概念)的人来说,这可能很难理解,所以我只是希望如果您阅读本文,您已经知道它是什么(至少,基本上))。
这里最棘手的问题是TProperty,为什么这里甚至需要它。
好的,它是如何工作的:
- 首先,必须定义某种类型以从
Func (Exspression<Func>) 语句返回。如果您不太在意,那也可能只是 object 类型:Expression<Func<TModel, TProperty>> expression;
- 如果您改用
object(并且不使用TProperty,但仍将其保留在方法名称声明中),则TProperty运行时类型将无法解析,您应该在方法上显式解析调用(这显然在开发中没有意义);
- 如果你离开它并且在调用方法时有类似的表达式作为传递:
Html.TextBoxFor(model => model.SomeValue),编译器解析TProperty类型,你不必在方法调用上指定它的类型,否则您必须执行以下操作:Html.TextBoxFor<ATypeOfYourModel, string>(model => model.SomeValue)(在这种情况下为string == TProperty)。
在 TProperty 的运行时类型被解析后,您会在 VisualStudio 工具提示(或 Resharper 提示)中将语法高亮显示为 '(this HtmlHelper htmlHelper, Expression> expression)'
这里最重要的区别还在于我们可以指定可用于TProperty Prototype 的类型限制(下面的“AND:”部分)。
关于方法调用,再说一遍。如果我们的对象如下(例如):
var html = new HtmlHelper<CustomModel>();
var car = new Car<Milk>();
那么我们必须这样调用我们的扩展方法:
html.TextBoxFor<CustomModel, string>(model => model.SomeValue);
car.AddATrailer<Milk,ParticularMeasureUnitsType>
(theCarProduct => theCarProduct.MeasureUnits);
但是如果所有通用类型模板运行时类型都已解析(在我们的例子中是这样的(因为Car 知道它是由其定义(new Car<Milk>())创建的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,我们将无法控制我们可以传递和不能传递给方法的内容(同理,在哪些方法上允许调用扩展方法等等)哪个不)。
我相信,这里可能还有一些需要改进或更正的地方,所以,请给你的 cmets 提供这方面的信息 -- 我很乐意修改主题。