Accelerate C in FPGA_8

Chia sẻ: Up Upload | Ngày: | Loại File: PDF | Số trang:59

lượt xem

Accelerate C in FPGA_8

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Tham khảo tài liệu 'accelerate c in fpga_8', công nghệ thông tin, kỹ thuật lập trình phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Chủ đề:

Nội dung Text: Accelerate C in FPGA_8

  1. CHAPTER 14 ■ EXTENSION METHODS 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 Transform( this IEnumerable input, Func op ) { foreach( var item in input ) { yield return op( item ); } } } public class TransformExample { static IEnumerable 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( DivideByThree ); var squareNumber = 502
  2. CHAPTER 14 ■ EXENTENSION METHODS new Func( 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 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 extension method, similar to Transform, 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. Also, the System.Linq.Queryable class provides the same extension methods for types that implement IQueryable, which derives from IEnumerable. 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 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, 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. For example, imagine a two-dimensional matrix implemented as a List 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() { 503
  3. CHAPTER 14 ■ EXTENSION METHODS var matrix = new List { new List { 1, 2, 3 }, new List { 4, 5, 6 }, new List { 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 GetRowMajorIterator( this List matrix ) { foreach( var row in matrix ) { foreach( var item in row ) { yield return item; } } } } public class IteratorExample { static void Main() { var matrix = new List { new List { 1, 2, 3 }, new List { 4, 5, 6 }, new List { 7, 8, 9 } }; // A more elegant way to enumerate the items. foreach( var item in matrix.GetRowMajorIterator() ) { Console.Write( "{0}, ", item ); } Console.WriteLine(); } } 504
  4. CHAPTER 14 ■ EXENTENSION METHODS In this version, I have externalized the iteration into the GetRowMajorIterator 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 Head { get; } IList Tail { get; } } ■ Caution Although I have named this type IList for this example, be sure not to confuse it with IList 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 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) 505
  5. CHAPTER 14 ■ EXTENSION METHODS In the following example code, I create a custom list called MyList that implements IList. 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 Head { get; } IList Tail { get; } } public class MyList : IList { public static IList CreateList( IEnumerable items ) { IEnumerator iter = items.GetEnumerator(); return CreateList( iter ); } public static IList CreateList( IEnumerator iter ) { if( !iter.MoveNext() ) { return new MyList( default(T), null ); } return new MyList( iter.Current, CreateList(iter) ); } private MyList( T head, IList tail ) { this.head = head; this.tail = tail; } public T Head { get { return head; } } public IList Tail { get { return tail; } } private T head; private IList tail; } public static class CustomIterators { public static IEnumerable LinkListIterator( this IList theList ) { 506
  6. CHAPTER 14 ■ EXENTENSION METHODS for( var list = theList; list.Tail != null; list = list.Tail ) { yield return list.Head; } } } public class IteratorExample { static void Main() { var listInts = new List { 1, 2, 3, 4 }; var linkList = MyList.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 using a List. The CreateList static method recursively populates MyList using these values. Once CreateList is finished, we have an instance of MyList 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 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 { 507
  7. CHAPTER 14 ■ EXTENSION METHODS public static IEnumerable GeneralIterator( this IList theList, Func finalState, Func incrementer ) { while( !finalState(theList) ) { yield return theList.Head; theList = incrementer( theList ); } } } public class IteratorExample { static void Main() { var listInts = new List { 1, 2, 3, 4 }; var linkList = MyList.CreateList( listInts ); var iterator = linkList.GeneralIterator( delegate( IList list ) { return list.Tail == null; }, delegate( IList list ) { return list.Tail; } ); foreach( var item in iterator ) { Console.Write( "{0}, ", item ); } Console.WriteLine(); } } Notice that the GeneralIterator 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 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 type, consider how we could create an extension method to reverse the list and return a new IList. 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 custom method implementation: 508
  8. CHAPTER 14 ■ EXENTENSION METHODS public static class CustomIterators { public static IList Reverse( this IList theList ) { var reverseList = new List(); Func reverseFunc = null; reverseFunc = delegate(IList list) { if( list != null ) { reverseFunc( list.Tail ); if( list.Tail != null ) { reverseList.Add( list.Head ); } } return reverseList; }; return MyList.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 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 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 is made public, you can eliminate the temporary List entirely and build the new MyList using a captured variable as shown here: public static class CustomIterators { public static IList Reverse( this IList theList ) { var reverseList = new MyList(default(T), null); Func reverseFunc = null; reverseFunc = delegate(IList list) { if( list.Tail != null ) { reverseList = new MyList( 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). 509
  9. CHAPTER 14 ■ EXTENSION METHODS }; return reverseFunc(theList); } } The previous Reverse 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. 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 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, 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 method uses only the stack as a temporary storage location while building the new reversed list: public static class CustomIterators { public static IList Reverse( this IList theList ) { Func reverseFunc = null; reverseFunc = delegate(IList list, IList result) { if( list.Tail != null ) { return reverseFunc( list.Tail, new MyList(list.Head, result) ); } return result; }; return reverseFunc(theList, new MyList(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 class used in the previous examples builds the linked list from the IEnumerable type entirely before the MyList object can be used. I used a List as the seed data, but I could have used anything that implements IEnumerable to fill the contents of MyList. But what if 510
  10. CHAPTER 14 ■ EXENTENSION METHODS IEnumerable 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.CreateList, you would have to kill the program forcefully or wait until your memory runs out as it tries to build the MyList. If you are creating a library for general use that contains a type such as MyList, which builds itself given some IEnumerable type, you should do your best to accommodate all scenarios that could be thrown at you. The IEnumerable 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." ); You can find Wes Dyer’s blog titled “Yet Another Language Geek” at 4 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. 511
  11. CHAPTER 14 ■ EXTENSION METHODS } public static void Validate( this SupplyCabinet cab ) { // Do something to validate the SupplyCabinet instance. Console.WriteLine( "Supply Cabinet Validated." ); } public static void Validate( this Employee emp ) { // Do something to validate the Employee instance. Console.WriteLine( "** Employee Failed Validation! **" ); } } } public class SupplyCabinet { } public class Employee { } public class MyApplication { static void Main() { String data = "some important data"; SupplyCabinet supplies = new SupplyCabinet(); Employee hrLady = new Employee(); data.Validate(); supplies.Validate(); hrLady.Validate(); } } Notice that for each type of object we want to validate (in this example there are three), I have defined a separate Validate extension method. The output from the application shows that the proper Validate method is being called for each instance and is as follows: String with "some important data" Validated. Supply Cabinet Validated. ** Employee Failed Validation! ** 512
  12. CHAPTER 14 ■ EXENTENSION METHODS In this example, it’s important to note that the visitors, in this case the extension methods named Validate, must treat the instance that they are validating as black boxes. By that I mean that they do not have the validation capabilities of a true instance method because only true instance methods have access to the internal state of the objects. Nevertheless, in this example, it might make sense to validate the instances from a client’s perspective. ■ Note Keep in mind that if the extension methods are defined in the same assembly as the types they are extended, they can still access internal members. 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 following code, which shows the changes marked in bold: 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." ); } public static void Validate( this Employee emp ) { // Do something to validate the Employee instance. Console.WriteLine( "** Employee Failed Validation! **" ); } public static void Validate( this T obj ) where T: IValidator { obj.DoValidation(); Console.WriteLine( "Instance of following type" + " validated: " + obj.GetType() ); } } } public interface IValidator { 513
  13. CHAPTER 14 ■ EXTENSION METHODS void DoValidation(); } public class SupplyCabinet : IValidator { public void DoValidation() { Console.WriteLine( "\tValidating SupplyCabinet" ); } } public class Employee { } public class MyApplication { static void Main() { String data = "some important data"; SupplyCabinet supplies = new SupplyCabinet(); Employee hrLady = new Employee(); data.Validate(); supplies.Validate(); hrLady.Validate(); } } Now, if the instance that we’re calling Validate on happens to implement IValidator, and there is not a version of Validate that specifically takes the type as its first parameter, the generic form of Validate will be called, which then passes through to the DoValidation method on the instance. Notice that I removed the extension method whose first parameter was of type SupplyCabinet, so that the compiler would choose the generic version. If I had left it in, the code as written in Main would call the version that I removed. However, even if I had not removed the nongeneric extension method, I could have forced the compiler to call the generic one by changing the syntax at the point of call, as shown here: public class MyApplication { static void Main() { String data = "some important data"; SupplyCabinet supplies = new SupplyCabinet(); Employee hrLady = new Employee(); data.Validate(); // Force generic version supplies.Validate(); 514
  14. CHAPTER 14 ■ EXENTENSION METHODS hrLady.Validate(); } } In the Main method, I have given the compiler more information to limit its search of the Validate method to generic forms of the extension method that accept one generic type parameter. Summary In this chapter, I introduced you to extension methods, including how to declare them, how to call them, and how the compiler implements them under the covers. Additionally, you saw how they really are just syntactic sugar and don’t require any changes to the underlying runtime in order to work. Extension methods can cause confusion when defined inappropriately, so we looked at some caveats to avoid. I showed you how they can be used to create useful things such as iterators (IEnumerable types) on containers that are not enumerable by default. Even for types that do have enumerators, you can define enumerators that iterate in a custom way. As you’ll see in Chapter 15, when they are combined with lambda expressions, extension methods provide a certain degree of expressiveness that is extremely useful. While showing how to create custom iterators, I took a slight detour (using anonymous functions rather than lambda expressions) to show you the world of functional programming that the features added to C# 3.0 unlock. The code for those examples will become much cleaner when you use lambda expressions instead of anonymous methods. In the next chapter, I’ll introduce you to lambda expressions, which really make functional programming in C# syntactically succinct. Additionally, they allow you to convert a functional expression into either code or data in the form of IL code or an expression tree, respectively. 515
  16. C H A P T E R 15 ■■■ Lambda Expressions Most of the new features of C# 3.0 opened up a world of expressive functional programming to the C# programmer. Functional programming, in its pure form, is a programming methodology built on top of immutable variables (sometimes called symbols), functions that can produce other functions, and recursion, just to name a few of its foundations. Some prominent functional programming languages include Lisp, Haskell, F#,1 and 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 in harmony. Lambda expressions are arguably the biggest piece of this functional programming pie. Introduction to Lambda Expressions Using lambda expressions, you can succinctly define function objects for use at any time. C# has always supported this capability via delegates, whereby you create a function object (in the form of a delegate) and wire it up to the backing code at the time of creation. Lambda expressions join these two actions— creation and connection—into one expressive statement in the code. Additionally, you can easily associate an environment with function objects using a construct called a closure. A functional is a function that takes functions in its parameter list and operates on those functions, possibly even returning another function as the result. For example, a functional could accept two functions, one performing one mathematical operation and the other performing a different mathematical operation, and return a third function that is a composite function built from the two. Lambda expressions provide a more natural way to create and invoke functionals. In simple syntactic terms, lambda expressions are a syntax whereby you can declare anonymous functions (delegates) in a more fluid and expressive way. At the same time, they are very much more than that, as you will see. Just about every use of an anonymous method can be replaced with a lambda 1 F# is an exciting new functional programming language for the .NET Framework. For more information, I invite you to read Robert Pickering’s Foundations of F# (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, 2004). 517
  17. CHAPTER 15 ■ LAMBDA EXPRESSIONS expression. That said, there’s no reason you can’t utilize functional programming techniques in C# 2.03. At first, the syntax of lambda expressions might take some time to get used to. Overall, the syntax is very straightforward when you are looking at a lambda expression all by itself. However, when embedded in code, they can be a little tricky to decipher and it might take some time to get used to their syntax. Lambda expressions really take two forms. The form that most directly replaces anonymous methods in syntax includes a statement block within braces. I like to refer to these as lambda statements. These lambda statements are a direct replacement for anonymous methods. Lambda expressions, on the other hand, provide an even more abbreviated way to declare an anonymous method and do not require code within braces nor a return statement. Both types of lambda expressions can be converted to delegates. However, lambda expressions without statement blocks offer something truly impressive. You can convert them into expression trees based on the types in the System.Linq.Expressions namespace. In other words, the function described in code is turned into data. I cover the topic of creating expression trees from lambda expressions in the section titled “Expression Trees” later in this chapter. Lambda Expressions and Closures First, let’s look at the simpler form of lambda expressions; that is, the ones without a statement block. As mentioned in the previous section, a lambda expression is a shorthand way of declaring a simple anonymous method. The following lambda expression can be used as a delegate that accepts one parameter and returns the result of performing division by 2 on that parameter: x => x / 2 What this says is “take x as a parameter and return the result of following operation on x.” Notice that the lambda expression is devoid of any type information. It does not mean that the expression is typeless. Instead, the compiler will deduce the type of the argument x and the type of the result depending on the context where it is used. It means that if you are assigning a lambda expression to a delegate, the types of the delegate definition are used to determine the types within the lambda expression. The following code shows what happens when a lambda expression is assigned to a delegate type: using System; using System.Linq; public class LambdaTest { static void Main() { Func expr = x => x / 2; int someNumber = 9; Console.WriteLine( "Result: {0}", expr(someNumber) ); } } I have marked the lambda expression in bold so that it stands out. The Func type is a helper type provided by the System namespace that you can use to declare simple delegates that take up to four arguments and return a result. In this case, I am declaring a variable expr that is a delegate that accepts 3 I covered some examples of functional programming with anonymous methods in Chapter 14. 518
  18. CHAPTER 15 ■ LAMBDA EXPRESSIONS an int and returns a double. When the compiler assigns the lambda expression to the expr variable, it uses the type information of the delegate to determine that the type of x must be int, and the type of the return value must be double. Now, if you execute that code, you’ll notice that the result is not entirely accurate. That is, the result has been rounded. This is expected because the result of x/2 is represented as an int, which is then cast to a double. You can fix this by specifying different types in the delegate declaration, as shown here: using System; using System.Linq; public class LambdaTest { static void Main() { Func expr = (double x) => x / 2; int someNumber = 9; Console.WriteLine( "Result: {0}", expr(someNumber) ); } } For the sake of demonstration, this lambda expression has what’s called an explicitly typed parameter list, and in this case, x is declared as type double. Also notice that the type of expr is now Func rather than Func. The compiler requires that any time you use a typed parameter list in a lambda expression and assign it to a delegate, the delegate’s argument types must match exactly. However, because an int is explicitly convertible to a double, you can pass someNumber to expr at call time as shown. ■ Note When using typed parameter lists, notice that the parameter list must be enclosed in parentheses. Parentheses are also required when declaring a delegate that accepts either more than one parameter or no parameters, as I’ll show later on. In fact, you can use parentheses at any time; they are optional in lambda expressions of only one implicitly typed parameter. When the lambda expression is assigned to a delegate, the return type of the expression is generally derived from the argument types. So, in the following code statement, the return type of the expression is double because the inferred type of the parameter x is double: Func expr = (x) => x / 2; // Compiler Error!!!! However, because double is not implicitly convertible to int, the compiler will complain: error CS1662: Cannot convert 'lambda expression' to delegate type 'System.Func' because some of the return 519
  19. CHAPTER 15 ■ LAMBDA EXPRESSIONS types in the block are not implicitly convertible to the delegate return type You can “fix” this by casting the result of the lambda expression body to int: Func expr = (x) => (int) x / 2; ■ Note Explicit types in lambda expression parameter lists are required if the delegate you are assigning them to has out or ref parameters. One could argue that fixing the parameter types explicitly within a lambda expression defeats some of the elegance of their expressive power. It definitely can make the code harder to read. Now I want to show you a simple lambda expression that accepts no parameters: using System; using System.Linq; public class LambdaTest { static void Main() { int counter = 0; WriteStream( () => counter++ ); Console.WriteLine( "Final value of counter: {0}", counter ); } static void WriteStream( Func generator ) { for( int i = 0; i < 10; ++i ) { Console.Write( "{0}, ", generator() ); } Console.WriteLine(); } } Notice how simple it was to pass a function as a parameter into the WriteStream method using this lambda expression. Moreover, the function passed in captures an environment within which to run, namely, the counter value in Main. This captured environment and the function together are commonly referred to as a closure. Finally, I want to show you an example of a lambda expression that accepts more than one parameter: using System; using System.Linq; using System.Collections.Generic; 520
  20. CHAPTER 15 ■ LAMBDA EXPRESSIONS public class LambdaTest { static void Main() { var teamMembers = new List { "Lou Loomis", "Smoke Porterhouse", "Danny Noonan", "Ty Webb" }; FindByFirstName( teamMembers, "Danny", (x, y) => x.Contains(y) ); } static void FindByFirstName( List members, string firstName, Func predicate ) { foreach( var member in members ) { if( predicate(member, firstName) ) { Console.WriteLine( member ); } } } } In this case, the lambda expression is used to create a delegate that accepts two parameters of type string and returns a bool. As you can see, lambda expressions provide a nice, succinct way of creating predicates. In a later section, “Iterators Revisited,” I’ll build upon an example from Chapter 14 showing how to use lambda expressions as predicates to create flexible iterators. Closures in C# 1.0 Back in the “good old days” of C# 1.0, creating closures was a painful process indeed, and one needed to do something like the following: using System; unsafe public class MyClosure { public MyClosure( int* counter ) { this.counter = counter; } public delegate int IncDelegate(); public IncDelegate GetDelegate() { return new IncDelegate( IncrementFunction ); } 521
Đồng bộ tài khoản