So, learning by teaching. This is what our life is about. he he
The question initially appeared to clarify some specific things I was not clear about the code posted, but after all the investigations, I guess, there is a sense to share the whole understanding I've got about generics, extension methods and expressions (if they are used all together).
That is pretty "scarry", but from other hands is also so beautifully about C#, when such a simple call of the method:
Html.TextBoxFor(model => model.SomeValue)
hides inside itself such reach and "deep" declaration, like:
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);
}
}
The code provided is decompiled version received with "Go to Implementation" with Resharper using. (Actually, I have to use "Navigate To/Decompiled Sources" after upgrading to Resharper 8, but this is not a place to talk about here -- mentioning just in case).
So, I will just try to explain the whole general "anathomy" of such methods definition (at least, which I know about that).
Goal:
To have an extension method which would extend certain generic class and allow to manipulated that class data (in complile time: having all the bonuses of Intellisense and refactoring) dependently on what class has been passed as a type argument to mentioned generic class.
In real life explanation, I would describe as the following: we have a class Car
(generic one), which is (generally) "suitable" for carrying different things like milk, cabbages, bycicles. But when we define something like Car<Milk>
, then that car can carry milk only. Also, we consider that that class building already finished (we can not change it). However, we also want to buy some trailer, which would carry exactly the same product as already defined for some car, like having "CreateATrailer" method. The way we would be able to determine the type of a trailer we need is with the product (Milk
in our case) measure units (as those are different for different products: litters, kilos, items). In this case, generic extension methods (possibly, with expressions using) are pretty handy. (That might be not an ideal real life example, but this is what came into my mind).
In short:
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);
}
}
Deep diving:
I won't be duplicating all the detailed explanations of generics, extension methods and expressions (actually, initially it's called "expressions trees"), which are widely available via google and msdn. But will just be more focused on how that would all work together.
public static class InputExtensions
There is nothing specific about that. Just any static class. Its name mostly does not play any role.
public static MvcHtmlString TextBoxFor<TModel, TProperty>
The method must be static.
If we don't use generics we ommit the <...> part completely. But if we do, we should specify there all the Types (Types Templates) which runtime types will be further either resolved by compiler automatically (dependently on parameters you would pass when you call the method), or must be defined explicitly, like someObject.OurExtensionMethod<string, int>
.
(this HtmlHelper<TModel> htmlHelper,
"this" modifier must be placed next to the first parameter and this is actually the sign that the method is an extension.
htmlHelper
parameter will be representing the object, on which we call the extension method. For easier understanding, it might be simply replaced with "@this" name like (this HtmlHelper<TModel> @this
. The only difference is just that you obviously don't have an access to any private member(s) of the class (as opposite to being 'inside' of the class to extend).
here is just a common generic type prototype -- nothing specific. It could be anything we want. Even string
, i.e. (this string @this,
.
Expression<Func<TModel, TProperty>> expression)
That's the trickiest part, as for me here.
So, if to speak of extension methods part, this will the first parameter which you would need to provide to the method on its call when extending some class.
As for Expression... We use it here to allow a user to pass some value, which further can be obtained (by the method inside) from an instance, which we provide to him. I.e., Func<TModel, TProperty>
(which is generally the same as Expression<Func<TModel, TProperty>>
(but not completely the same generally)) means, that on the method call we provide the user some ability to work with the type is the one that was used for our HtmlHelper instantiation as a Type parameter.
In other words, if we created an instance of Car<Milk>
(Milk
means TModel
in our case), then then we provide the Milk type to the caller, like Html.TextBoxFor(ourKindOfMilkObject => ourKindOfMilkObject.MeasureUnits)
, for instance.
(I guess, that might be pretty tricky to catch for people who are not familiar much with expressions (or even Func/Action concepts), so I just expect that if you read this you already know what it is (at least, basically)).
The trickiest question here is with TProperty
and why is it even needed in here.
Ok, how does it work:
- first of all, there must be some type defined to return from
Func
(Exspression<Func>
) statement. That might be also just object
type if you don't really care of that much: Expression<Func<TModel, TProperty>> expression
;
- if you to use
object
instead (and don't use TProperty
, but still leave it in the method name declaration), then TProperty
runtime type becomes unresolved, and you should it resolve explicitly on the method call (which is obviously, has no sense in development);
- if you leave it and have something like that as an expression to pass when calling the method:
Html.TextBoxFor(model => model.SomeValue)
, the compiler resolves the TProperty
type and you don't have to specify its type on the method call, otherwise you would have to do the following: Html.TextBoxFor<ATypeOfYourModel, string>(model => model.SomeValue)
(string
== TProperty
in this case).
right after the runtime type for TProperty
is resolved, you get the syntax highlighting in VisualStudio tooltip (or Resharper hints) as '(this HtmlHelper htmlHelper, Expression> expression)' instead.
the most important difference here also is that we can specify limitation of types which are available for using on TProperty
Prototype (the "AND:" section below).
About the method calling, again. If we have our object as follows (for instance):
var html = new HtmlHelper<CustomModel>();
var car = new Car<Milk>();
then we must call our extension method this way:
html.TextBoxFor<CustomModel, string>(model => model.SomeValue);
car.AddATrailer<Milk,ParticularMeasureUnitsType>
(theCarProduct => theCarProduct.MeasureUnits);
but if all the Generic Types Templates runtime types are resolved (which is in our case (because Car
is know that created with Milk
by its definition (new Car<Milk>()
) and the expression Func
return value type is also available from SomeValue
type definition), then we simplify:
html.TextBoxFor(model => model.SomeValue);
car.AddATrailer(theCarProduct => theCarProduct.MeasureUnits);
AND:
Incredibly important thing about Generics is also that we can define limitations of which class/interface can be used for the Generic Types Templates to use, with using a keyword where
, like:
(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
where TModel : CustomModel where some : ParticularValueType
Otherwise, if we use object
instead of TProperty, we would not be able to control what we can and what we can not pass into the method (the same way, on which methods to allow the extension methods calling and on which not to).
I believe, there are still might be some things to improve or correct in here, so, please give your comments on that -- I will be happy to modify the topic.