1. Trang chủ
  2. » Công Nghệ Thông Tin

Apress - Accelerated C#_8 ppt

59 319 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Cấu trúc

  • Home Page

  • Prelim

  • Contents at a Glance

  • Contents

  • About the Author

  • About the Technical Reviewer

  • Acknowledgments

  • Preface

    • About This Book

  • C# Preview

    • Differences Between C# and C++

      • C#

      • C++

      • CLR Garbage Collection

    • Example of a C# Program

    • Overview of Features Added in C# 2.0

    • Overview of Features Added in C# 3.0

    • Overview of New C# 4.0 Features

    • Summary

  • C# and the CLR

    • The JIT Compiler in the CLR

    • Assemblies and the Assembly Loader

      • Minimizing the Working Set of the Application

      • Naming Assemblies

      • Loading Assemblies

    • Metadata

    • Cross-Language Compatibility

    • Summary

  • C# Syntax Overview

    • C# Is a Strongly Typed Language

    • Expressions

    • Statements and Expressions

    • Types and Variables

      • Value Types

      • Enumerations

      • Flags Enumerations

      • Reference Types

      • Default Variable Initialization

      • Implicitly Typed Local Variables

      • Type Conversion

      • Array Covariance

      • Boxing Conversion

      • as and is Operators

      • Generics

    • Namespaces

      • Defining Namespaces

      • Using Namespaces

    • Control Flow

      • if-else, while, do-while, and for

      • switch

      • foreach

      • break, continue, goto, return, and throw

    • Summary

  • Classes, Structs, and Objects

    • Class Definitions

      • Fields

      • Constructors

      • Methods

      • Static Methods

      • Instance Methods

      • Properties

      • Declaring Properties

      • Accessors

      • Read-Only and Write-Only Properties

      • Auto-Implemented Properties

      • Encapsulation

      • Accessibility

      • Interfaces

      • Inheritance

      • Accessibility of Members

      • Implicit Conversion and a Taste of Polymorphism

      • Member Hiding

      • The base Keyword

      • sealed Classes

      • abstract Classes

      • Nested Classes

      • Indexers

      • partial Classes

      • partial Methods

      • Static Classes

      • Reserved Member Names

      • Reserved Names for Properties

      • Reserved Names for Indexers

      • Reserved Names for Destructors

      • Reserved Names for Events

    • Value Type Definitions

      • Constructors

      • The Meaning of this

      • Finalizers

      • Interfaces

    • Anonymous Types

    • Object Initializers

    • Boxing and Unboxing

      • When Boxing Occurs

      • Efficiency and Confusion

    • System.Object

      • Equality and What It Means

      • The IComparable Interface

    • Creating Objects

      • The new Keyword

      • Using new with Value Types

      • Using new with Class Types

      • Field Initialization

      • Static (Class) Constructors

      • Instance Constructor and Creation Ordering

    • Destroying Objects

      • Finalizers

      • Deterministic Destruction

      • Exception Handling

    • Disposable Objects

      • The IDisposable Interface

      • The using Keyword

    • Method Parameter Types

      • Value Arguments

      • ref Arguments

      • out Parameters

      • param Arrays

      • Method Overloading

      • Optional Arguments

      • Named Arguments

    • Inheritance and Virtual Methods

      • Virtual and Abstract Methods

      • override and new Methods

      • sealed Methods

      • A Final Few Words on C# Virtual Methods

    • Inheritance, Containment, and Delegation

      • Choosing Between Interface and Class Inheritance

      • Delegation and Composition vs. Inheritance

    • Summary

  • Interfaces and Contracts

    • Interfaces Define Types

    • Defining Interfaces

      • What Can Be in an Interface?

      • Interface Inheritance and Member Hiding

    • Implementing Interfaces

      • Implicit Interface Implementation

      • Explicit Interface Implementation

      • Overriding Interface Implementations in Derived Classes

      • Beware of Side Effects of Value Types Implementing Interfaces

    • Interface Member Matching Rules

    • Explicit Interface Implementation with Value Types

    • Versioning Considerations

    • Contracts

      • Contracts Implemented with Classes

      • Interface Contracts

    • Choosing Between Interfaces and Classes

    • Summary

  • Overloading Operators

    • Just Because You Can Doesn’t Mean You Should

    • Types and Formats of Overloaded Operators

    • Operators Shouldn’t Mutate Their Operands

    • Does Parameter Order Matter?

    • Overloading the Addition Operator

    • Operators That Can Be Overloaded

      • Comparison Operators

      • Conversion Operators

      • Boolean Operators

    • Summary

  • Exception Handling and Exception Safety

    • How the CLR Treats Exceptions

    • Mechanics of Handling Exceptions in C#

      • Throwing Exceptions

      • Changes with Unhandled Exceptions Starting with .NET 2.0

      • Syntax Overview of the try, catch, and finally Statements

      • Rethrowing Exceptions and Translating Exceptions

      • Exceptions Thrown in finally Blocks

      • Exceptions Thrown in Finalizers

      • Exceptions Thrown in Static Constructors

    • Who Should Handle Exceptions?

    • Avoid Using Exceptions to Control Flow

    • Achieving Exception Neutrality

      • Basic Structure of Exception-Neutral Code

      • Constrained Execution Regions

      • Critical Finalizers and SafeHandle

    • Creating Custom Exception Classes

    • Working with Allocated Resources and Exceptions

    • Providing Rollback Behavior

    • Summary

  • Working with Strings

    • String Overview

    • String Literals

    • Format Specifiers and Globalization

      • Object.ToString, IFormattable, and CultureInfo

      • Creating and Registering Custom CultureInfo Types

      • Format Strings

      • Console.WriteLine and String.Format

      • Examples of String Formatting in Custom Types

      • ICustomFormatter

      • Comparing Strings

    • Working with Strings from Outside Sources

    • StringBuilder

    • Searching Strings with Regular Expressions

      • Searching with Regular Expressions

      • Searching and Grouping

      • Replacing Text with Regex

      • Regex Creation Options

    • Summary

  • Arrays, Collection Types, and Iterators

    • Introduction to Arrays

      • Implicitly Typed Arrays

      • Type Convertibility and Covariance

      • Sortability and Searchability

      • Synchronization

      • Vectors vs. Arrays

    • Multidimensional Rectangular Arrays

    • Multidimensional Jagged Arrays

    • Collection Types

      • Comparing ICollection<T> with ICollection

      • Collection Synchronization

      • Lists

      • Dictionaries

      • Sets

      • System.Collections.ObjectModel

      • Efficiency

    • IEnumerable<T>, IEnumerator<T>, IEnumerable, and IEnumerator

      • Types That Produce Collections

    • Iterators

      • Forward, Reverse, and Bidirectional Iterators

    • Collection Initializers

    • Summary

  • Delegates, Anonymous Functions, and Events

    • Overview of Delegates

    • Delegate Creation and Use

      • Single Delegate

      • Delegate Chaining

      • Iterating Through Delegate Chains

      • Unbound (Open Instance) Delegates

    • Events

    • Anonymous Methods

      • Captured Variables and Closures

      • Beware the Captured Variable Surprise

      • Anonymous Methods as Delegate Parameter Binders

    • The Strategy Pattern

    • Summary

  • Generics

    • Difference Between Generics and C++ Templates

    • Efficiency and Type Safety of Generics

    • Generic Type Definitions and Constructed Types

      • Generic Classes and Structs

      • Generic Interfaces

      • Generic Methods

      • Generic Delegates

      • Generic Type Conversion

      • Default Value Expression

      • Nullable Types

      • Constructed Types Control Accessibility

      • Generics and Inheritance

    • Constraints

      • Constraints on Nonclass Types

    • Coand Contravariance

      • Covariance

      • Contravariance

      • Invariance

      • Variance and Delegates

    • Generic System Collections

    • Generic System Interfaces

    • Select Problems and Solutions

      • Conversion and Operators within Generic Types

      • Creating Constructed Types Dynamically

    • Summary

  • Threading in C#

    • Threading in C# and .NET

      • Starting Threads

      • Passing Data to New Threads

      • Using ParameterizedThreadStart

      • The IOU Pattern and Asynchronous Method Calls

      • States of a Thread

      • Terminating Threads

      • Halting Threads and Waking Sleeping Threads

      • Waiting for a Thread to Exit

      • Foreground and Background Threads

      • Thread-Local Storage

      • How Unmanaged Threads and COM Apartments Fit In

    • Synchronizing Work Between Threads

      • Lightweight Synchronization with the Interlocked Class

      • SpinLock Class

      • Monitor Class

      • Beware of Boxing

      • Pulse and Wait

      • Locking Objects

      • ReaderWriterLock

      • ReaderWriterLockSlim

      • Mutex

      • Semaphore

      • Events

      • Win32 Synchronization Objects and WaitHandle

    • Using ThreadPool

      • Asynchronous Method Calls

      • Timers

    • Concurrent Programming

      • Task Class

      • Parallel Class

      • Easy Entry to the Thread Pool

    • Thread-Safe Collection Classes

    • Summary

  • In Search of C# Canonical Forms

    • Reference Type Canonical Forms

      • Default to sealed Classes

      • Use the Non-Virtual Interface (NVI) Pattern

      • Is the Object Cloneable?

      • Is the Object Disposable?

      • Does the Object Need a Finalizer?

      • What Does Equality Mean for This Object?

      • Reference Types and Identity Equality

      • Value Equality

      • Overriding Object.Equals for Reference Types

      • If You Override Equals, Override GetHashCode Too

      • Does the Object Support Ordering?

      • Is the Object Formattable?

      • Is the Object Convertible?

      • Prefer Type Safety at All Times

      • Using Immutable Reference Types

    • Value Type Canonical Forms

      • Override Equals for Better Performance

      • Do Values of This Type Support Any Interfaces?

      • Implement Type-Safe Forms of Interface Members and Derived Methods

    • Summary

      • Checklist for Reference Types

      • Checklist for Value Types

  • Extension Methods

    • Introduction to Extension Methods

      • How Does the Compiler Find Extension Methods?

      • Under the Covers

      • Code Readability versus Code Understandability

    • Recommendations for Use

      • Consider Extension Methods Over Inheritance

      • Isolate Extension Methods in Separate Namespace

      • Changing a Type’s Contract Can Break Extension Methods

    • Transforms

    • Operation Chaining

    • Custom Iterators

      • Borrowing from Functional Programming

    • The Visitor Pattern

    • Summary

  • Lambda Expressions

    • Introduction to Lambda Expressions

      • Lambda Expressions and Closures

      • Closures in C# 1.0

      • Closures in C# 2.0

      • Lambda Statements

    • Expression Trees

      • Operating on Expressions

      • Functions as Data

    • Useful Applications of Lambda Expressions

      • Iterators and Generators Revisited

      • More on Closures (Variable Capture) and Memoization

      • Currying

      • Anonymous Recursion

    • Summary

  • LINQ: Language Integrated Query

    • A Bridge to Data

      • Query Expressions

      • Extension Methods and Lambda Expressions Revisited

    • Standard Query Operators

    • C# Query Keywords

      • The from Clause and Range Variables

      • The join Clause

      • The where Clause and Filters

      • The orderby Clause

      • The select Clause and Projection

      • The let Clause

      • The group Clause

      • The into Clause and Continuations

    • The Virtues of Being Lazy

      • C# Iterators Foster Laziness

      • Subverting Laziness

      • Executing Queries Immediately

      • Expression Trees Revisited

    • Techniques from Functional Programming

      • Custom Standard Query Operators and Lazy Evaluation

      • Replacing foreach Statements

    • Summary

  • Dynamic Types

    • What does dynamic Mean?

    • How Does dynamic Work?

      • The Great Unification

      • Call Sites

      • Objects with Custom Dynamic Behavior

      • Efficiency

      • Boxing with Dynamic

    • Dynamic Conversions

      • Implicit Dynamic Expressions Conversion

    • Dynamic Overload Resolution

    • Dynamic Inheritance

      • You Cannot Derive from dynamic

      • You Cannot Implement dynamic Interfaces

      • You Can Derive From Dynamic Base Types

    • Duck Typing in C#

    • Limitations of dynamic Types

    • ExpandoObject: Creating Objects Dynamically

    • Summary

  • Index

    • Symbols

    • A

    • B

    • C

    • D

    • E

    • F

    • G

    • H

    • I

    • J

    • K

    • L

    • M

    • N

    • O

    • P

    • Q

    • R

    • S

    • T

    • V

    • U

    • W

    • X

    • Y

Nội dung

CHAPTER 14 ■ EXTENSION METHODS 502 changed. C#, on the other hand, offers a hybrid environment in which you are free to implement functional programming if you choose. Also, those familiar with the Standard Template Library (STL) will get a familiar feeling from this style of programming. STL swept through the C++ programming community back in the early 1990s and encouraged a more functional programming thought process. Operation Chaining Using extension methods, operation chaining becomes a more natural process. Again, it’s nothing that you could not have done in the C# 2.0 days using plain static methods and anonymous methods. However, with the streamlined syntax, chaining actually removes the clutter and can trigger some innovative thinking. Let’s start with the example from the previous section, in which we took a list of integers and transformed them into a list of doubles. This time, we’ll look at how we can actually chain operations in a fluid way. Let’s suppose that after dividing the integers by 3, we want to then compute the square of the result. The following code shows how to do that: using System; using System.Linq; using System.Collections.Generic; public static class MyExtensions { public static IEnumerable<R> Transform<T, R>( this IEnumerable<T> input, Func<T, R> op ) { foreach( var item in input ) { yield return op( item ); } } } public class TransformExample { static IEnumerable<int> CreateInfiniteList() { int n = 0; while( true ) yield return n++; } static double DivideByThree( int n ) { return (double)n / 3; } static double Square( double r ) { return r * r; } static void Main() { var divideByThree = new Func<int, double>( DivideByThree ); var squareNumber = CHAPTER 14 ■ EXENTENSION METHODS 503 new Func<double, double>( Square ); var result = CreateInfiniteList(). Transform( divideByThree ). Transform( squareNumber ); var iter = result.GetEnumerator(); for( int i = 0; i < 25; ++i ) { iter.MoveNext(); Console.WriteLine( iter.Current ); } } } Isn’t that cool? In one statement of code, I took an infinite list of integers and applied a divisor followed by a squaring operation, and the end result is a lazy-evaluated IEnumerable<double> type that computes each element as needed. Functional programming is actually pretty useful when you look at it this way. Of course, you could chain as many operations as necessary. For example, you might want to append a rounding operation at the end. Or maybe you want to append a filtering operation so that only the results that match a certain criteria are considered. To do that, you could create a generic Filter<T> extension method, similar to Transform<T>, that takes a predicate delegate as a parameter and uses it to filter the items in the collection. At this point, I’m sure that you’re thinking of all the really useful extension methods you could create to manipulate data. You might be wondering if a host of these extension methods already exists. Check out the System.Linq.Enumerable class. This class provides a whole host of extension methods that are typically used with LINQ, which I cover in Chapter 16. All these extension methods operate on types of IEnumerable<T>. Also, the System.Linq.Queryable class provides the same extension methods for types that implement IQueryable<T>, which derives from IEnumerable<T>. Custom Iterators Chapter 9 covered iterators, which were added to the language in C# 2.0. I described some ways you could create custom iterators. Extension methods offer even more flexibility to create custom iterators for collections in a very expressive way. By default, every collection that implements IEnumerable or IEnumerable<T> has a forward iterator, so a custom iterator would be necessary to walk through a collection in a different way than its default iterator. Also, you will need to create a custom iterator for types that don’t support IEnumerable<T>, as I’ll show in the next section, “Borrowing from Functional Programming.” Let’s look at how you can use extension methods to implement custom iterators on types implementing IEnumerable<T>. For example, imagine a two-dimensional matrix implemented as a List<List<int>> type. When performing some operations on such matrices, it’s common to require an iterator that walks through the matrix in row-major fashion. What that means is that the iterator walks all the items of the first row, then the second row, and so on until it reaches the end of the last row. You could iterate through the matrix in row-major form as shown here: using System; using System.Collections.Generic; public class IteratorExample { static void Main() { CHAPTER 14 ■ EXTENSION METHODS 504 var matrix = new List<List<int>> { new List<int> { 1, 2, 3 }, new List<int> { 4, 5, 6 }, new List<int> { 7, 8, 9 } }; // One way of iterating the matrix. foreach( var list in matrix ) { foreach( var item in list ) { Console.Write( "{0}, ", item ); } } Console.WriteLine(); } } Yes, this code gets the job done, but it is not very reusable. Let’s see one way this can be redone using an extension method: using System; using System.Collections.Generic; public static class CustomIterators { public static IEnumerable<T> GetRowMajorIterator<T>( this List<List<T>> matrix ) { foreach( var row in matrix ) { foreach( var item in row ) { yield return item; } } } } public class IteratorExample { static void Main() { var matrix = new List<List<int>> { new List<int> { 1, 2, 3 }, new List<int> { 4, 5, 6 }, new List<int> { 7, 8, 9 } }; // A more elegant way to enumerate the items. foreach( var item in matrix.GetRowMajorIterator() ) { Console.Write( "{0}, ", item ); } Console.WriteLine(); } } CHAPTER 14 ■ EXENTENSION METHODS 505 In this version, I have externalized the iteration into the GetRowMajorIterator<T> extension method. At the same time, I made the extension method generic so it will accept two-dimensional nested lists that contain any type, thus making it a bit more reusable. Borrowing from Functional Programming You might have already noticed that many of the new features added in C# 3.0 facilitate a functional programming model. You’ve always been able to implement functional programming models in C#, but the new language features make it easier syntactically by making the language more expressive. Sometimes, the functional model facilitates easier solutions to various problems. Various languages are categorized as functional languages, and Lisp is one of them. If you’ve ever programmed using Lisp, you know that the list is one of the core constructs in that language. In C#, we can model such a list using the following interface definition at the core: public interface IList<T> { T Head { get; } IList<T> Tail { get; } } ■ Caution Although I have named this type IList<T> for this example, be sure not to confuse it with IList<T> in the System.Collections.Generic namespace. If one were to implement this type as written, it would be best to define it within one’s own namespace to avoid name conflict. After all, that is one of the benefits of using namespaces. The structure of this list is a bit different from the average linked list implementation. Notice that instead of one node containing a value and a pointer to the next node, it instead contains the value at the node and then a reference to the rest of the list. In fact, it’s rather recursive in nature. That’s no surprise because recursive techniques are part of the functional programming model. For example, if you were to represent a list on paper by writing values within parentheses, a traditional list might look like the following: (1 2 3 4 5 6) Whereas a list defined using the IList<T> interface above could look like this: (1 (2 (3 (4 (5 (6 (null null))))))) Each set of parentheses contains two items: the value of the node and then the remainder of the list within a nested set of parentheses. So, to represent a list with just one item in it, such as just the number 1, we could represent it this way: (1 (null null)) And of course, the empty list could be represented this way: (null null) CHAPTER 14 ■ EXTENSION METHODS 506 In the following example code, I create a custom list called MyList<T> that implements IList<T>. The way it is built here is not very efficient, and I’ll have more to say about that shortly. using System; using System.Collections.Generic; public interface IList<T> { T Head { get; } IList<T> Tail { get; } } public class MyList<T> : IList<T> { public static IList<T> CreateList( IEnumerable<T> items ) { IEnumerator<T> iter = items.GetEnumerator(); return CreateList( iter ); } public static IList<T> CreateList( IEnumerator<T> iter ) { if( !iter.MoveNext() ) { return new MyList<T>( default(T), null ); } return new MyList<T>( iter.Current, CreateList(iter) ); } private MyList( T head, IList<T> tail ) { this.head = head; this.tail = tail; } public T Head { get { return head; } } public IList<T> Tail { get { return tail; } } private T head; private IList<T> tail; } public static class CustomIterators { public static IEnumerable<T> LinkListIterator<T>( this IList<T> theList ) { CHAPTER 14 ■ EXENTENSION METHODS 507 for( var list = theList; list.Tail != null; list = list.Tail ) { yield return list.Head; } } } public class IteratorExample { static void Main() { var listInts = new List<int> { 1, 2, 3, 4 }; var linkList = MyList<int>.CreateList( listInts ); foreach( var item in linkList.LinkListIterator() ) { Console.Write( "{0}, ", item ); } Console.WriteLine(); } } First, notice in Main that I am initializing an instance of MyList<int> using a List<int>. The CreateList static method recursively populates MyList<int> using these values. Once CreateList is finished, we have an instance of MyList<int> that can be visualized as follows: (1 (2 (3 (4 (null null))))) You’re probably wondering why the list is not represented using the following: (1 (2 (3 (4 null)))) You could do that; however, you will find that it is not as easy to use either when composing the list or consuming it. Speaking of consuming the list, you can imagine that there are times when you need to iterate over one of these lists. In that case, you need a custom iterator, which I have highlighted in the example. The code in Main uses this iterator to send all the list items to the console. The output is as follows: 1, 2, 3, 4, In the example, notice that the LinkListIterator<T> method creates a forward iterator by making some assumptions about how to determine whether it has reached the end of the list and how to increment the cursor during iteration. That is, it starts at the head and assumed it has finished iterating once the current node’s tail is null. What if we externalized this information? For example, what if we wanted to allow the user to parameterize what it means to iterate, such as iterate forwards, backwards, circularly, and so on? How could we do that? If the idea of delegates pops into your mind, you’re right on track. Check out the following revised version of the iterator extension method and the Main method: public static class CustomIterators { CHAPTER 14 ■ EXTENSION METHODS 508 public static IEnumerable<T> GeneralIterator<T>( this IList<T> theList, Func<IList<T>, bool> finalState, Func<IList<T>, IList<T>> incrementer ) { while( !finalState(theList) ) { yield return theList.Head; theList = incrementer( theList ); } } } public class IteratorExample { static void Main() { var listInts = new List<int> { 1, 2, 3, 4 }; var linkList = MyList<int>.CreateList( listInts ); var iterator = linkList.GeneralIterator( delegate( IList<int> list ) { return list.Tail == null; }, delegate( IList<int> list ) { return list.Tail; } ); foreach( var item in iterator ) { Console.Write( "{0}, ", item ); } Console.WriteLine(); } } Notice that the GeneralIterator<T> method accepts two more delegates, one of which is then called upon to check whether the cursor is at the end of the list, and the other to increment the cursor. In the Main method, I am passing two delegates in the form of anonymous methods. Now the GeneralIterator<T> method can be used to iterate over every other item in the list simply by modifying the delegate passed in through the incrementer parameter. ■ Note Some of you might already be familiar with lambda expressions, which were introduced in C# 3.0. Indeed, when using lambda expressions, you can clean up this code considerably by using the lambda expression syntax to replace the previous anonymous delegates. I cover lambda expressions in Chapter 15. As a final extension method example for operations on the IList<T> type, consider how we could create an extension method to reverse the list and return a new IList<T>. There are several ways one could consider doing this, and some are much more efficient than others. However, I want to show you an example that uses a form of recursion. Consider the following Reverse<T> custom method implementation: CHAPTER 14 ■ EXENTENSION METHODS 509 public static class CustomIterators { public static IList<T> Reverse<T>( this IList<T> theList ) { var reverseList = new List<T>(); Func<IList<T>, List<T>> reverseFunc = null; reverseFunc = delegate(IList<T> list) { if( list != null ) { reverseFunc( list.Tail ); if( list.Tail != null ) { reverseList.Add( list.Head ); } } return reverseList; }; return MyList<T>.CreateList( reverseFunc(theList) ); } } If you’ve never encountered this style of coding, it can surely make your brain twist inside your head. The key to the work lies in the fact that there is a delegate defined that calls itself and captures variables along the way. 3 In the preceding code, the anonymous method is assigned to the reverseFunc variable. And as you can see, the anonymous method body calls reverseFunc, or more accurately, itself! In a way, the anonymous method captures itself! The trigger to get all the work done is in the last line of the Reverse<> method. It initiates the chain of recursive calls to the anonymous method and then passes the resulting List<T> to the CreateList method, thus creating the reversed list. Those who pay close attention to efficiency are likely pointing out the inefficiency of creating a temporary List<T> instance that is then passed to CreateList in Main. After all, if the original list is huge, creating a temporary list to just throw away moments later will put pressure on the garbage collected heap, among other things. For example, if the constructor to MyList<T> is made public, you can eliminate the temporary List<T> entirely and build the new MyList<T> using a captured variable as shown here: public static class CustomIterators { public static IList<T> Reverse<T>( this IList<T> theList ) { var reverseList = new MyList<T>(default(T), null); Func<IList<T>, MyList<T>> reverseFunc = null; reverseFunc = delegate(IList<T> list) { if( list.Tail != null ) { reverseList = new MyList<T>( list.Head, reverseList ); reverseFunc( list.Tail ); } return reverseList; 3 Computer science wonks like to call a delegate that captures variables a closure, which is a construct in which a function is packaged with an environment (such as variables). CHAPTER 14 ■ EXTENSION METHODS 510 }; return reverseFunc(theList); } } The previous Reverse<T> method first creates an anonymous function and stores it in the local variable reverseFunc. It then returns the results of calling the anonymous method to the caller of Reverse<T>. All the work of building the reversed list is encapsulated into the closure created by the anonymous method and the captured local variables reverseList and reverseFunc. reverseFunc simply calls itself recursively until it is finished building the reversed list into the reverseList captured variable. Those of you who are more familiar with functional programming are probably saying that the preceding Reverse<T> extension method can be cleaned up by eliminating the captured variable and using the stack instead. In this case, it’s more of a stylistic change, but I want to show it to you for completeness’ sake. Instead of having the captured variable reverseList, as in the previous implementation of Reverse<T>, I instead pass the reference to the list I am building as an argument to each recursion of the anonymous method reverseFunc. Why would you want to do this? By eliminating the captured variable reverseList, you eliminate the possibility that the reference could be inadvertently changed outside of the scope of the anonymous method. Therefore, my final example of the Reverse<T> method uses only the stack as a temporary storage location while building the new reversed list: public static class CustomIterators { public static IList<T> Reverse<T>( this IList<T> theList ) { Func<IList<T>, IList<T>, IList<T>> reverseFunc = null; reverseFunc = delegate(IList<T> list, IList<T> result) { if( list.Tail != null ) { return reverseFunc( list.Tail, new MyList<T>(list.Head, result) ); } return result; }; return reverseFunc(theList, new MyList<T>(default(T), null)); } } ■ Note This code uses the Func<> definition, which is a generic delegate that is defined in the System namespace. Using Func<> is a shortcut you can employ to avoid having to declare delegate types all over the place. You use the Func<> type parameter list to declare what the parameter types (if any) and return type of the delegate are. If the delegate you need has no return value, you can use the Action<> generic delegate type. The MyList<T> class used in the previous examples builds the linked list from the IEnumerable<T> type entirely before the MyList<T> object can be used. I used a List<T> as the seed data, but I could have used anything that implements IEnumerable<T> to fill the contents of MyList<T>. But what if CHAPTER 14 ■ EXENTENSION METHODS 511 IEnumerable<T> were an infinite iterator similar to the one created by CreateInfiniteList in the “Operation Chaining” section of this chapter? If you fed the result of CreateInfiniteList to MyList<T>.CreateList, you would have to kill the program forcefully or wait until your memory runs out as it tries to build the MyList<T>. If you are creating a library for general use that contains a type such as MyList<T>, which builds itself given some IEnumerable<T> type, you should do your best to accommodate all scenarios that could be thrown at you. The IEnumerable<T> given to you could take a very long time to calculate each item of the enumeration. For example, it could be enumerating over a database of live data in which database access is very costly. For an example of how to create the list in a lazy fashion, in which each node is created only when needed, check out Wes Dyer’s excellent blog, specifically the entry titled “Why all the love for lists?” 4 The technique of lazy evaluation while iterating is a fundamental feature of LINQ, which I cover in Chapter 16. The Visitor Pattern The Visitor pattern, as described in the seminal pattern book Design Patterns: Elements of Reusable Object-Oriented Software by the Gang of Four, 5 allows you to define a new operation on a group of classes without changing the classes. Extension methods present a handy option for implementing the Visitor pattern. For example, consider a collection of types that might or might not be related by inheritance, and imagine that you want to add functionality to validate instances of them at some point in your application. One option, although very unattractive, is to modify the public contract of all the types, introducing a Validate method on each of them. One might even jump to the conclusion that the easiest way to do it is to introduce a new base type that derives from System.Object, implements Validate as an abstract method, and then makes all the other types derive from the new type instead of System.Object. That would be nothing more than a maintenance nightmare in the end. By now, you should agree that an extension method or a collection of extension methods will do the trick nicely. Given a collection of unrelated types, you will probably implement a host of extension methods. But the beauty is that you don’t have to change the already defined types. In fact, if they’re not your types to begin with, you cannot change them anyway. Consider the following code: using System; using ValidatorExtensions; namespace ValidatorExtensions { public static class Validators { public static void Validate( this String str ) { // Do something to validate the String instance. Console.WriteLine( "String with \"" + str + "\" Validated." ); 4 You can find Wes Dyer’s blog titled “Yet Another Language Geek” at blogs.msdn.com/wesdyer/. 5 Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Boston, MA: Addison-Wesley Professional, 1995), is cited in the references at the end of this book. [...]... to LINQ in Chapter 16, but for our discussion here, the most important fact is that LINQ provides a language-native, expressive syntax for describing operations on data that are not naturally modeled in an object-oriented way For example, you can create a LINQ expression to search a large in-memory array (or any other IEnumerable type) for items that match a certain pattern LINQ is extensible and can... Main method as follows: static void Main() { Func fib = null; fib = (x) => x > 1 ? fib(x-1) + fib(x-2) : x; fib = fib.Memoize(); Func fibConstant = null; fibConstant = (x) => { if( x == 1 ) { return 1 / ((decimal)fib(x)); } else { return 1 / ((decimal)fib(x)) + fibConstant(x-1); } }; fibConstant = fibConstant.Memoize(); Console.WriteLine( "\n{0}\t{1}\t{2}\t{3}\n", "Count",... CLR.4 Then, in the Main method, I created an instance of MyClosure and passed a delegate created by calling GetDelegate to WriteStream What a lot of work! And on top of that, it sure makes for some hard-to-follow code ■ Note You might be wondering why I used a pointer in the preceding longhand example, thus forcing one to compile using the /unsafe compiler option The reason was simply to emphasize the... Using generics and constraints, you can slightly extend the previous example and provide a generic form of the Validate extension method that can be used if the instance supports a well-known interface In this case, the well-known interface is named IValidator Therefore, it would be nice to create a special Validate method that will be called if the type implements the IValidator interface Consider the... first come up with an operation to compute the nth Fibonacci number: using System; using System.Linq; public class Proof { static void Main() { Func fib = null; fib = (x) => x > 1 ? fib(x-1) + fib(x-2) : x; for( int i = 30; i < 40; ++i ) { Console.WriteLine( fib(i) ); } } } When you look at this code, the first thing that jumps up and grabs you is the formation of the Fibonacci routine; that... cache.TryGetValue(x, out result) ) { return result; } result = func(x); cache[x] = result; return result; }; } } public class Proof { static void Main() { Func fib = null; fib = (x) => x > 1 ? fib(x-1) + fib(x-2) : x; fib = fib.Memoize(); for( int i = 30; i < 40; ++i ) { Console.WriteLine( fib(i) ); } } } First of all, notice that in Main, I have added only one more statement where I apply the Memoize... (Berkeley, CA: Apress, 2007) 2 One of the languages that I use often is C++ Those of you that are familiar with metaprogramming in C++ are definitely familiar with functional programming techniques If you do use C++ and you’re curious about metaprogramming, I invite you to check out David Abrahams’ and Alexey Gurtovoy’s most excellent book C++ Template Metaprogramming (Boston: Addison-Wesley Professional,... Scheme.2 However, functional programming does not require a pure functional language, and one can use and implement functional programming disciplines in traditionally imperative languages such as the C-based languages (including C#) The features added in C# 3.0 transformed the language into a more expressive hybrid language in which both imperative and functional programming techniques can be utilized... read more about memoization at http://en.wikipedia.org/wiki/Memoization Also, Wes Dyer has an excellent entry regarding memoization on his blog at http://blogs.msdn.com/wesdyer/archive/2007/01/26/function-memoization.aspx 8 535 CHAPTER 15 ■ LAMBDA EXPRESSIONS using System; using System.Linq; using System.Collections.Generic; public static class Memoizers { public static Func Memoize( this Func... infinitely, and it would stop only if you experienced an overflow exception or you stopped execution But the items you are iterating over don’t exist as a collection; rather, they are generated on an as-needed basis each time you advance the iterator You can apply this concept in many ways, even creating a random number generator implemented using C# iterators More on Closures (Variable Capture) and . programming disciplines in traditionally imperative languages such as the C-based languages (including C#) . The features added in C# 3.0 transformed the language into a more expressive hybrid language. (Boston: Addison-Wesley Professional, 2004). CHAPTER 15 ■ LAMBDA EXPRESSIONS 5 18 expression. That said, there’s no reason you can’t utilize functional programming techniques in C# 2.0 3 . At. the Validate extension method that can be used if the instance supports a well-known interface. In this case, the well-known interface is named IValidator. Therefore, it would be nice to create

Ngày đăng: 19/06/2014, 22:20

TỪ KHÓA LIÊN QUAN