Iv. the essence of Extension method
As described in the previous section, we know how to define a extension method in C #: It is a static method that is defined in a static class and the first parameter is marked as the This keyword. In this section, we will learn more about extension Method.
Similar to other new features of C # 3.0, Extension method is just a new feature of the. NET programming language in C #. We know that C # is a typical compiled language, and the source code we write must be compiled into assembly with C # compiler before it can be loaded by the CLR, JIT compiled into machine instruction, and eventually executed. These new features of C # 3.0 mostly affect the source being compiled by C # compiler into the assembly phase, in other words, these new features are just compiler. By correcting the compiler, he was prompted to compile the new syntax introduced by C # 3.0 into the corresponding IL code, in essence, there is no essential difference between these IL code and the original IL. All of the CLR is unaware of these new features when it is compiled to generate assembly by the CLR when it is loaded and executed.
From the definition of Extension method, we can see that Extension method is essentially a static method. But we tend to call them in the instance method. The role of C # compiler is obvious: a source code called by instance method is compiled into IL code that corresponds to the traditional static method invocation.
Although extension method is essentially a static method member of static class, it differs from the traditional static method in that it adds a this keyword before the first parameter. Now let's look at the subtle differences between them. Let's start by defining a general static Method:
public static vector Adds (vector v, vector v1)
{
return new Vector {X = v.x + v1. X, Y = v.y + v1. Y};
}
Note: the definition of vector is described in the new features of in-depth understanding of C # 3.0 (2): Extension Method-part I.
Let's take a look at the IL generated by compiling with compiler:
. method private hidebysig static void Main (string[] args) cil managed
{
. entrypoint
//Code size (0x32) . Maxstack 2
. Locals init ([0] class Artech.ExtensionMethod.Vector V,
[1] class Artech.ExtensionMethod.Vector ' & Lt;>g__initlocal0 ')
Il_0000:nop
Il_0001:newobj instance void Artech.extensionmethod.vector::.ctor ()
Il_0006:stloc.1
Il_0007:ldloc.1
IL_0008:ldc.r8 1.
Il_0011:callvirt instance void artech.extensionmethod.vector::set_x (float64)
Il_0016:nop
Il_0017:ldloc.1
IL_0018:ldc.r8 2.
Il_0021:callvirt instance void artech.extensionmethod.vector::set_y (float64)
Il_0026:nop
il_0027: Ldloc.1
il_0028:stloc.0
il_0029:ldloc.0
il_002a:ldloc.0
Il_002b:call class Artech.ExtensionMethod.Vector Artech.extensionmethod.extension::adds (class Artech.ExtensionMethod.Vector,
Class Artech.ExtensionMethod.Vector)
il_0030:stloc.0
Il_0031:ret
}//End of method Program::main
For people who know IL, the above IL code should be easy to understand.
Let's take a look at the extension method defined in the following way:
public static Class Extension
{
public static vector Adds (this vector v, vector v1)
{
return new Vector {X = v.x + v1. X, Y = v.y + v1. Y};
}
}
For the IL, the following:
. method public hidebysig Static class Artech.ExtensionMethod.Vector
Adds (class Artech.ExtensionMethod.Vector V,
Class Artech.ExtensionMethod.Vector v1) CIL managed
{
. custom instance void [System.core]system.runtime.compilerservices.extensionattribute::.ctor () = (01 00 00 00)
Code size (0x35)
. maxstack 3
. Locals init ([0] class Artech.ExtensionMethod.Vector ' <>g__initlocal0 ',
[1] class Artech.ExtensionMethod.Vector cs$1$0000)
Il_0000:nop
il_0001:newobj instance void Artech.extensionmethod.vector::.ctor ()
il_0006:stloc.0
il_0007:ldloc.0
il_0008:ldarg.0
Il_0009:callvirt instance float64 artech.extensionmethod.vector::get_x ()
Il_000e:ldarg.1
Il_000f:callvirt instance float64 artech.extensionmethod.vector::get_x ()
Il_0014:add
Il_0015:callvirt instance void artech.extensionmethod.vector::set_x (float64)
Il_001a:nop
il_001b:ldloc.0
il_001c:ldarg.0
Il_001d:callvirt instance float64 artech.extensionmethod.vector::get_y ()
Il_0022:ldarg.1
Il_0023:callvirt instance float64 artech.extensionmethod.vector::get_y ()
Il_0028:add
Il_0029:callvirt instance void artech.extensionmethod.vector::set_y (float64)
Il_002e:nop
il_002f:ldloc.0
Il_0030:stloc.1
IL_0031:BR.S il_0033
Il_0033:ldloc.1
Il_0034:ret
}//End of method Extension::adds
By comparison, we find that the only difference between the IL generated by the general static method defined above is that the following code was added at the beginning of the adds method definition:
. custom instance void [System.core]system.runtime.compilerservices.extensionattribute::.ctor () = (01 00 00 00)
This added IL code is obvious, which is to add a customer Attribute:System.Runtime.CompilerServices.ExtensionAttribute on the adds method. The extensionattribute has the following definitions:
[AttributeUsage (AttributeTargets.Method | AttributeTargets.Class | attributetargets.assembly)]
public sealed class Extensionattribute:attribute
{
}
So the following is the definition of extension method
public static vector Adds (this vector v, vector v1)
{
return new Vector {X = v.x + v1. X, Y = v.y + v1. Y};
}
And the definition below is equivalent
[ExtensionAttribute]
public static vector Adds (vector v, vector v1)
{
return new Vector {X = v.x + v1. X, Y = v.y + v1. Y};
}
However, System.Runtime.CompilerServices.ExtensionAttribute is not the same as other custom attribute because it is defined for extension method, We can only define extension Method by adding the syntax of this Key word. So when we apply System.Runtime.CompilerServices.ExtensionAttribute directly to the adds method, the following compile error appears:
Don't use ' System.Runtime.CompilerServices.ExtensionAttribute '. Use the ' this ' keyword instead.
Above we compare extension method itself il and general static method IL, now let's see when we call extension method in instance method of IL. Suppose we call adds in the following way.
Class Program
{
static void Main (string[] args)
{
var v = new Vector {X = 1, Y = 2};
v = v.adds (v);
}
}
The following is the Il of Main method:
. method private hidebysig static void Main (string[] args) cil managed
{
. entrypoint
//Code size (0x32) . Maxstack 2
. Locals init ([0] class Artech.ExtensionMethod.Vector V,
[1] class Artech.ExtensionMethod.Vector ' & Lt;>g__initlocal0 ')
Il_0000:nop
Il_0001:newobj instance void Artech.extensionmethod.vector::.ctor ()
Il_0006:stloc.1
Il_0007:ldloc.1
IL_0008:ldc.r8 1.
Il_0011:callvirt instance void artech.extensionmethod.vector::set_x (float64)
Il_0016:nop
Il_0017:ldloc.1
IL_0018:ldc.r8 2.
Il_0021:callvirt instance void artech.extensionmethod.vector::set_y (float64)
Il_0026:nop
il_0027: Ldloc.1
il_0028:stloc.0
il_0029:ldloc.0
il_002a:ldloc.0
Il_002b:call class Artech.ExtensionMethod.Vector Artech.extensionmethod.extension::adds (class Artech.ExtensionMethod.Vector,
Class Artech.ExtensionMethod.Vector)
il_0030:stloc.0
Il_0031:ret
}//End of method Program::main
With the above IL, we see that the adds method of Artech.ExtensionMethod.Extension is called.
Il_002b:call Class Artech.ExtensionMethod.Vector Artech.extensionmethod.extension::adds (class Artech.ExtensionMethod.Vector,
Class Artech.ExtensionMethod.Vector)
Through the analysis of IL, we basically see the essence of extension method. Let's take a brief look at the compilation process for compiler: When compiler compiles the call to the adds method, it must determine whether the adds method is a member of the vector type or defined as extension method. The Extension method has the lowest priority, and compiler will see in the referenced namespace if the corresponding namespace is defined in the vector if there are no adds methods defined in it. The static Class of Extension method. A compilation error occurs when the post is found for the appropriate compilation.
How far complete extension method's sample
After introducing the essence of extension method, we further understand the use of extension method through a relatively complete sample, and we can also get a rough idea of the principles of LINQ through this sample.
C # 3.0 defines a series of Operator:select for LINQ, From,where,orderby ..., prompting us to handle a wide variety of data in oo ways, such as Xml,relational DB data,c# The ienumeratable<t> Object. Like what:
var names = new List<string> {"Tom Cruise", "Tom Hanks", "Al Pacino", "Harrison Ford"};
var result = names. Where (name = = name. StartsWith ("Tom"));
foreach (var name in result)
{
Console.WriteLine (name);
}
We use the code above to filter the name (first name) to Tom's name from a list of names ("Tom Cruise", "Tom Hanks", "Al Pacino", "Harrison Ford"). Through the where Operator, pass in a filter condition represented by lambda expression (name = = name. StartsWith ("Tom")). Where operator is defined by means of extension method.
The sample provided here is to define a operator that accomplishes the same function as where operator, and we name this operator when.
Using System;
Using System.Collections.Generic;
Using System.Linq;
Using System.Text;
Using System.Collections;
Namespace Artech.extensionmethod
{
Public delegate TResult Function<tparam, tresult> (Tparam param);
public static Class Extension
{
public static ienumerable<tsource> when<tsource> (this ienumerable<tsource> source, function< TSource, bool> predicate)
{
return new whenenumerator<tsource> (source, predicate);
}
}
public class whenenumerator<tsource>: Ienumerable<tsource>, ienumerator<tsource>
{
Private ienumerable<tsource> _source;
Private Function<tsource, bool> _predicate;
Private ienumerator<tsource> _sourceenumerator;
Public Whenenumerator (ienumerable<tsource> source, Function<tsource, bool> predicate)
{
This._source = source;
This._predicate = predicate;
This._sourceenumerator = This._source. GetEnumerator ();
}
IEnumerable members#region ienumerable<tsource> Members
Public ienumerator<tsource> GetEnumerator ()
{
return new Whenenumerator<tsource> (This._source, this._predicate);
}
#endregion
IEnumerable members#region IEnumerable Members
IEnumerator Ienumerable.getenumerator ()
{
throw new Exception ("The method or operation is not implemented.");
}
#endregion
IEnumerator members#region ienumerator<tsource> Members
Public TSource Current
{
get {return this._sourceenumerator.current;}
}
#endregion
IDisposable members#region IDisposable Members
public void Dispose ()
{
throw new Exception ("The method or operation is not implemented.");
}
#endregion
IEnumerator members#region IEnumerator Members
Object IEnumerator.Current
{
Get
{
return this._sourceenumerator.current;
}
}
public bool MoveNext ()
{
if (!this._sourceenumerator.movenext ())
{
return false;
}
while (!this._predicate (this._sourceenumerator.current))
{
if (!this._sourceenumerator.movenext ())
{
return false;
}
}
return true;
}
public void Reset ()
{
This._sourceenumerator.reset ();
}
#endregion
}
}
Let's take a look at the definition of our new LINQ Operator:when. I first defined a generic delegate:function. In fact, he defines a unary function y = f (x), Tparam and TResult as arguments and returns a worthwhile type.
Public delegate TResult Function<tparam, tresult> (Tparam param);
The extension method:when is then defined in the static Class extesnion. The method contains two parameters, one of which is the data source that performs the filtering, and the other is an assertion that determines whether each object of the data source meets the filter criteria that you define. Returns a Whenenumerator object that we have customized to implement the IEnumerable.
public static Class Extension
{
public static ienumerable<tsource> when<tsource> (this ienumerable<tsource> source, function< TSource, bool> predicate)
{
return new whenenumerator<tsource> (source, predicate);
}
}
The definition of Whenenumerator is the key to achieving the When Extension method, and we now focus on its implementation in detail. Whenenumerator implements the interface Enumerable<t> For simplicity, we also define the implementation of the enumerator corresponding to the same class, So Whenenumerator realized two interface:ienumerable<tsource> ienumerator<tsource>.
The following 3 members represent: The data source used to perform the filtering, the assertion that determines whether the filter is met, and the enumerator object for the data source.
Private ienumerable<tsource> _source;
Private Function<tsource, bool> _predicate;
Private ienumerator<tsource> _sourceenumerator;
By returning a Whenenumerator object, the Ienumerable<tsource> GetEnumerator () method is implemented.
Public ienumerator<tsource> GetEnumerator ()
{
return new Whenenumerator<tsource> (This._source, this._predicate);
}
For another interface Ienumerator<tsource>, the method that directly invokes the enumerator of the data source implements the current, and reset (). The MoveNext () is implemented in the following way: Set the current position on the next element that satisfies the filter criteria.
public bool MoveNext ()
{
if (!this._sourceenumerator.movenext ())
{
return false;
}
while (!this._predicate (this._sourceenumerator.current))
{
if (!this._sourceenumerator.movenext ())
{
return false;
}
}
return true;
}
So far, this new LINQ operator has been created, and now we can invoke when by using where operator.
We can use the when Operator by delegate way:
Class Program
{
static void Main ()
{
var names = new List<string> {"Tom Cruise", "Tom Hanks", "Al Pacino", "Harrison Ford"};
var result = names. When (delegate (string name) {return name. StartsWith ("Tom"); });
foreach (var name in result)
{
Console.WriteLine (name);
}
}
}
Output Result:
Tom Cruise
Tom Hanks
We can also use the When Operator using lambda expression:
static void Main ()
{
var names = new List<string> {"Tom Cruise", "Tom Hanks", "Al Pacino", "Harrison Ford"};
var result = names. When (Name=>name. StartsWith ("Tom"));
foreach (var name in result)
{
Console.WriteLine (name);
}
}
This is obviously a more concise approach.
Deferred Evaluation
For LINQ, there is a very important feature: Deferred Evaluation. Before we understand this feature, let's look at an example:
static void Main ()
{
var names = new List<string> {"Tom Cruise", "Tom Hanks", "Al Pacino", "Harrison Ford"};
var result1 = names. When (Name=>name. StartsWith ("Tom"));
Names[0] = "Stephen Chou";
var result2 = names. When (name = = name. StartsWith ("Tom"));
foreach (var name in RESULT1)
{
Console.WriteLine (name);
}
foreach (var name in result2)
{
Console.WriteLine (name);
}
}
Run the program and you'll see that two foreach loops show the same result: Tom Hanks. Why did RESULT1 return the first element before it was changed, but the result of our final output is that it reflects the changed data source. With our definition above, you can easily get the answer. What I'm going to say here is an important feature of LINQ deferred Evaluation: There is no data acquisition process when calling operator, and the task at this stage is to create an expression that is identical to the fetch data. As long as you're really using this data, use the expression you built in the heavy data source to get the data through the query.
Extension method[Next Article]