Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Extension Methods

The best way to understand C#’s extension methods feature is by way of an example. In the “String­ Builder Members” section in Chapter 14, “Chars, Strings, and Working with Text,” I mention how the StringBuilder class offers fewer methods than the String class for manipulating a string and how strange this is, considering that the StringBuilder class is the preferred way of manipulating a string because it is mutable. So, let’s say that you would like to define some of these missing methods yourself to operate on a StringBuilder. For example, you might want to define your own IndexOf method as follows.

 

public static class StringBuilderExtensions {

public static Int32 IndexOf(StringBuilder sb, Char value) { for (Int32 index = 0; index < sb.Length; index++)

if (sb[index] == value) return index; return ­1;

}

}

 

Now that you have defined this method, you can use it as the following code demonstrates.

 

StringBuilder sb = new StringBuilder("Hello. My name is Jeff."); // The initial string

 

// Change period to exclamation and get # characters in 1st sentence (5). Int32 index = StringBuilderExtensions.IndexOf(sb.Replace('.', '!'), '!');

 

This code works just fine, but is it not ideal from a programmer’s perspective. The first problem is

that a programmer who wants to get the index of a character within a StringBuilder must know


that the StringBuilderExtensions class even exists. The second problem is that the code does not reflect the order of operations that are being performed on the StringBuilder object, making the code difficult to write, read, and maintain. The programmer wants to call Replace first and then call IndexOf; but when you read the last line of code from left to right, IndexOf appears first on the line and Replace appears second. Of course, you could alleviate this problem and make the code’s behavior more understandable by rewriting it like this.

 

// First, change period to exclamation mark sb.Replace('.', '!');

 

// Now, get # characters in 1st sentence (5)

Int32 index = StringBuilderExtensions.IndexOf(sb, '!');

 

However, a third problem exists with both versions of this code that affects understanding the code’s behavior. The use of StringBuilderExtensions is overpowering and detracts a program- mer’s mind from the operation that is being performed: IndexOf. If the StringBuilder class had defined its own IndexOf method, then we could rewrite the code above as follows.

 

// Change period to exclamation and get # characters in 1st sentence (5). Int32 index = sb.Replace('.', '!').IndexOf('!');

 

Wow, look how great this is in terms of code maintainability! In the StringBuilder object, we’re

going to replace a period with an exclamation mark and then find the index of the exclamation mark.

 

Now, I can explain what C#’s extension methods feature does. It allows you to define a static method that you can invoke using instance method syntax. Or, in other words, we can now define our own IndexOf method and the three problems mentioned above go away. To turn the IndexOf method into an extension method, we simply add the this keyword before the first argument.



 

public static class StringBuilderExtensions {

public static Int32 IndexOf(this StringBuilder sb, Char value) { for (Int32 index = 0; index < sb.Length; index++)

if (sb[index] == value) return index; return ­1;

}

}

 

Now, when the compiler sees code like the following, the compiler first checks if the String­ Builder class or any of its base classes offers an instance method called IndexOf that takes a single Char parameter.

 

Int32 index = sb.IndexOf('X');

 

If an existing instance method exists, then the compiler produces IL code to call it. If no matching in- stance method exists, then the compiler will look at any static classes that define static methods called IndexOf that take as their first parameter a type matching the type of the expression being used to invoke the method. This type must also be marked with the this keyword. In this example, the expres- sion is sb, which is of the StringBuilder type. In this case, the compiler is looking specifically for an IndexOf method that takes two parameters: a StringBuilder (marked with the this keyword) and a Char. The compiler will find our IndexOf method and produce IL code that calls our static method.


OK—so this now explains how the compiler improves the last two problems related to code under- standability that I mentioned earlier. However, I haven’t yet addressed the first problem: how does a programmer know that an IndexOf method even exists that can operate on a StringBuilder ob- ject? The answer to this question is found in Microsoft Visual Studio’s IntelliSense feature. In the edi- tor, when you type a period, Visual Studio’s IntelliSense window opens to show you the list of instance methods that are available. Well, that IntelliSense window also shows you any extension methods that exist for the type of expression you have to the left of the period. Figure 8-1 shows Visual Studio’s IntelliSense window; the icon for an extension method has a down arrow next to it, and the tooltip next to the method indicates that the method is really an extension method. This is truly awesome because it is now easy to define your own methods to operate on various types of objects and have other programmers discover your methods naturally when using objects of these types.

 
 

FIGURE 8-1Visual Studio’s IntelliSense window, showing extension methods.

 

 

Rules and Guidelines

There are some additional rules and guidelines that you should know about extension methods:

 

■ C# supports extension methods only; it does not offer extension properties, extension events, extension operators, and so on.

■ Extension methods (methods with this before their first argument) must be declared in non-

generic, static classes. However, there is no restriction on the name of the class; you can call it whatever you want. Of course, an extension method must have at least one parameter, and only the first parameter can be marked with the this keyword.


■ The C# compiler looks only for extension methods defined in static classes that are themselves defined at the file scope. In other words, if you define the static class nested within another class, the C# compiler will emit the following message: error CS1109: Extension method must be defined in a top­level static class; StringBuilderExtensions is a nested class.

 

■ Because the static classes can have any name you want, it takes the C# compiler time to find extension methods because it must look at all the file-scope static classes and scan their static methods for a match. To improve performance and also to avoid considering an extension method that you may not want, the C# compiler requires that you “import” extension meth- ods. For example, if someone has defined a StringBuilderExtensions class in a Win­ tellect namespace, then a programmer who wants to have access to this class’s extension methods must put a using Wintellect; directive at the top of his or her source code file.

■ It is possible that multiple static classes could define the same extension method. If the com- piler detects that two or more extension methods exist, then the compiler issues the following message: error CS0121: The call is ambiguous between the following methods or properties: 'StringBuilderExtensions.IndexOf(string, char)' and 'An­ otherStringBuilderExtensions.IndexOf(string, char)'. To fix this error, you must modify your source code. Specifically, you cannot use the instance method syntax to call this static method anymore; instead you must now use the static method syntax where you explic- itly indicate the name of the static class to explicitly tell the compiler which method you want to invoke.

■ You should use this feature sparingly, because not all programmers are familiar with it. For ex- ample, when you extend a type with an extension method, you are actually extending derived types with this method as well. Therefore, you should not define an extension method whose first parameter is System.Object, because this method will be callable for all expression types and this will really pollute Visual Studio’s IntelliSense window.

■ There is a potential versioning problem that exists with extension methods. If, in the future, Microsoft adds an IndexOf instance method to their StringBuilder class with the same prototype as my code is attempting to call, then when I recompile my code, the compiler will bind to Microsoft’s IndexOf instance method instead of my static IndexOf method. Because of this, my program will experience different behavior. This versioning problem is another reason why this feature should be used sparingly.

 

Extending Various Types with Extension Methods

In this chapter, I demonstrated how to define an extension method for a class, StringBuilder. I’d like to point out that because an extension method is really the invocation of a static method, the CLR does not emit code ensuring that the value of the expression used to invoke the method is not null.

 

// sb is null StringBuilder sb = null;


// Calling extension method: NullReferenceException will NOT be thrown when calling IndexOf

// NullReferenceException will be thrown inside IndexOf’s for loop sb.IndexOf('X');

 

// Calling instance method: NullReferenceException WILL be thrown when calling Replace sb.Replace('.', '!');

 

I’d also like to point out that you can define extension methods for interface types, as the follow- ing code shows.

 

public static void ShowItems<T>(this IEnumerable<T> collection) { foreach (var item in collection)

Console.WriteLine(item);

}

 

The extension method above can now be invoked using any expression that results in a type that implements the IEnumerable<T> interface.

 

public static void Main() {

// Shows each Char on a separate line in the console "Grant".ShowItems();

 

// Shows each String on a separate line in the console new[] { "Jeff", "Kristin" }.ShowItems();

 

// Shows each Int32 value on a separate line in the console new List<Int32>() { 1, 2, 3 }.ShowItems();

}

       
   
 
 

 

You can define extension methods for delegate types, too. Here is an example.

 

public static void InvokeAndCatch<TException>(this Action<Object> d, Object o) where TException : Exception {

try { d(o); }

catch (TException) { }

}

 

And here is an example of how to invoke it.

 

Action<Object> action = o => Console.WriteLine(o.GetType()); // Throws NullReferenceException action.InvokeAndCatch<NullReferenceException>(null); // Swallows NullReferenceException

 

You can also add extension methods to enumerated types. I show an example of this in the “Add- ing Methods to Enumerated Types” section in Chapter 15, “Enumerated Types and Bit Flags.”


And last but not least, I want to point out that the C# compiler allows you to create a delegate (see

Chapter 17, “Delegates,” for more information) that refers to an extension method over an object.

 

public static void Main () {

// Create an Action delegate that refers to the static ShowItems extension method

// and has the first argument initialized to reference the "Jeff" string. Action a = "Jeff".ShowItems;

.

.

.

// Invoke the delegate that calls ShowItems passing it a reference to the "Jeff" string. a();

}

 

In the preceding code, the C# compiler generates IL code to construct an Action delegate. When creating a delegate, the constructor is passed the method that should be called and is also passed

a reference to an object that should be passed to the method’s hidden this parameter. Normally, when you create a delegate that refers to a static method, the object reference is null because static methods don’t have a this parameter. However, in this example, the C# compiler generated some special code that creates a delegate that refers to a static method (ShowItems) and the target object of the static method is the reference to the “Jeff” string. Later, when the delegate is invoked, the CLR will call the static method and will pass to it the reference to the “Jeff” string. This is a little hacky, but it works great and it feels natural so long as you don’t think about what is happening internally.

 

The Extension Attribute

It would be best if this concept of extension methods was not C#-specific. Specifically, we want programmers to define a set of extension methods in some programming language and for people in other programming languages to take advantage of them. For this to work, the compiler of choice must support searching static types and methods for potentially matching extension methods. And compilers need to do this quickly so that compilation time is kept to a minimum.

In C#, when you mark a static method’s first parameter with the this keyword, the compiler in- ternally applies a custom attribute to the method and this attribute is persisted in the resulting file’s metadata. The attribute is defined in the System.Core.dll assembly, and it looks like this.

 

// Defined in the System.Runtime.CompilerServices namespace [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] public sealed class ExtensionAttribute : Attribute {

}

 

In addition, this attribute is applied to the metadata for any static class that contains at least one extension method. And this attribute is also applied to the metadata for any assembly that contains at least one static class that contains an extension method. So now, when compiling code that invokes an instance method that doesn’t exist, the compiler can quickly scan all the referenced assemblies to know which ones contain extension methods. Then it can scan only these assemblies for static classes that contain extension methods, and it can scan just the extension methods for potential matches to compile the code as quickly as possible.


 

 

 
 

Partial Methods

Imagine that you use a tool that produces a C# source code file containing a type definition. The tool knows that there are potential places within the code it produces where you might want to custom- ize the type’s behavior. Normally, customization would be done by having the tool-produced code invoke virtual methods. The tool-produced code would also have to contain definitions for these vir- tual methods, and the way these methods would be implemented is to do nothing and simply return. Now, if you want to customize the behavior of the class, you’d define your own class, derive it from the base class, and then override any virtual methods implementing it so that it has the behavior you desire. Here is an example.

 

// Tool­produced code in some source code file: internal class Base {

private String m_name;

 

// Called before changing the m_name field

protected virtual void OnNameChanging(String value) {

}

 

public String Name {

get { return m_name; } set {

OnNameChanging(value.ToUpper()); // Inform class of potential change m_name = value; // Change the field

}

}

}

 

 

// Developer­produced code in some other source code file: internal class Derived : Base {

protected override void OnNameChanging(string value) { if (String.IsNullOrEmpty(value))

throw new ArgumentNullException("value");

}

}


Unfortunately, there are two problems with the preceding code:

 

■ The type must be a class that is not sealed. You cannot use this technique for sealed classes or for value types (because value types are implicitly sealed). In addition, you cannot use this technique for static methods because they cannot be overridden.

■ There are efficiency problems here. A type is being defined just to override a method; this wastes a small amount of system resources. And, even if you do not want to override the be- havior of OnNameChanging, the base class code still invokes a virtual method that simply does nothing but return. Also, ToUpper is called whether OnNameChanging accesses the argument passed to it or not.

C#’s partial methods feature allows you the option of overriding the behavior or a type while fixing the aforementioned problems. The code below uses partial methods to accomplish the same seman- tic as the previous code.

 

// Tool­produced code in some source code file: internal sealed partial class Base {

private String m_name;

 

// This defining­partial­method­declaration is called before changing the m_name field partial void OnNameChanging(String value);

 

public String Name {

get { return m_name; } set {

OnNameChanging(value.ToUpper()); // Inform class of potential change m_name = value; // Change the field

}

}

}

 

// Developer­produced code in some other source code file: internal sealed partial class Base {

 

// This implementing­partial­method­declaration is called before m_name is changed partial void OnNameChanging(String value) {

if (String.IsNullOrEmpty(value))

throw new ArgumentNullException("value");

}

}

 

There are several things to notice about this new version of the code:

 

■ The class is now sealed (although it doesn’t have to be). In fact, the class could be a static class or even a value type.

■ The tool-produced code and the developer-produced code are really two partial definitions that ultimately make up one type definition. For more information about partial types, see the “Partial Classes, Structures, and Interfaces” section in Chapter 6, “Type and Member Basics.”


■ The tool-produced code defined a partial method declaration. This method is marked with the

partial token and it has no body.

 

■ The developer-produced code implemented the partial method declaration. This method is also marked with the partial token and it has a body.

Now, when you compile this code, you see the same effect as the original code I showed you.

Again, the big benefit here is that you can rerun the tool and produce new code in a new source code file, but your code remains in a separate file and is unaffected. And, this technique works for sealed classes, static classes, and value types.

       
   
 
 

 

But, there is another big improvement we get with partial methods. Let’s say that you do not need to modify the behavior of the tool-produced type. In this case, you do not supply your source code file at all. If you just compile the tool-produced code by itself, the compiler produces IL code and metadata as if the tool-produced code looked like this.

 

// Logical equivalent of tool­produced code if there is no

// implementing partial method declaration: internal sealed class Base {

private String m_name;

 

public String Name {

get { return m_name; } set {

m_name = value; // Change the field

}

}

}

 

That is, if there is no implementing partial method declaration, the compiler will not emit any metadata representing the partial method. In addition, the compiler will not emit any IL instructions to call the partial method. And the compiler will not emit code that evaluates any arguments that would have been passed to the partial method. In this example, the compiler will not emit code to call the ToUpper method. The result is that there is less metadata/IL, and the run-time performance is awesome!

       
   
 
 


Rules and Guidelines

There are some additional rules and guidelines that you should know about partial methods:

 

■ They can only be declared within a partial class or struct.

 

■ Partial methods must always have a return type of void, and they cannot have any parameters marked with the out modifier. These restrictions are in place because at run time, the method may not exist and so you can’t initialize a variable to what the method might return because the method might not exist. Similarly, you can’t have an out parameter because the method would have to initialize it and the method might not exist. A partial method may have ref parameters, may be generic, may be instance or static, and may be marked as unsafe.

■ Of course, the defining partial method declaration and the implementing partial method dec- laration must have identical signatures. If both have custom attributes applied to them, then the compiler combines both methods’ attributes together. Any attributes applied to a param- eter are also combined.

■ If there is no implementing partial method declaration, then you cannot have any code that attempts to create a delegate that refers to the partial method. Again, the reason is that the method doesn’t exist at run time. The compiler produces this message: error CS0762: Can­ not create delegate from method 'Base.OnNameChanging(string)' because it is a partial method without an implementing declaration.

■ Partial methods are always considered to be private methods. However, the C# compiler for- bids you from putting the private keyword before the partial method declaration.


 


C HA P T E R 9

Parameters

In this chapter:

Optional and Named Parameters............................................................. 209

Implicitly Typed Local Variables.............................................................. 212

Passing Parameters by Reference to a Method....................................... 214

Passing a Variable Number of Arguments to a Method............................. 220

Parameter and Return Type Guidelines................................................... 223

Const-ness.......................................................................................... 224

 

This chapter focuses on the various ways of passing parameters to a method, including how to op- tionally specify parameters, specify parameters by name, and pass parameters by reference, as well as how to define methods that accept a variable number of arguments.

 

 


Date: 2016-03-03; view: 661


<== previous page | next page ==>
Nbsp;   Conversion Operator Methods | Rules and Guidelines
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.019 sec.)