Browse by Tags

All Tags » lambdas (RSS)

Getting Func-y with Lambdas

Let's say we've got some information stored somewhere (database, XML, file – it doesn't matter) about individuals.  For simplicity, let's look at a class that represents this data: 1: class Person 2: { 3: public string FirstName { get; set; } 4: public string LastName { get; set; } 5: public string EmailAddress { get; set; } 6:   7: public static Person FindByEmailAddress( string emailAddress) 8: { 9: // implementation omitted 10: } 11:   12: public void Save() 13: { 14: // implementation omitted 15: } 16: } We've got some exported CSV data that needs to be merged into this set of data.  However, the information from the CSV file has multiple email address' per person.  Here's the class that represents each row of CSV data: 1: class CSVData 2: { 3: public string FirstName { get; set; } 4: public string LastName { get; set; } 5: public string HomeEmail { get; set; } 6: public string WorkEmail { get; set; } 7: } Some people in the CSV data will have only a home email, some will have only a work email, and some will have both.  I can easily get a couple of lists that contain the people with email address' defined via LINQ (note: the implementation of LoadCSVData is not important here): 1: IList<CSVData> csvData = LoadCSVData(); 2: var peopleWithHomeEmail = from c in csvData where c.HomeEmail.Length > 0 select c; 3: var peopleWithWorkEmail = from c in csvData where c.WorkEmail.Length > 0 select c; Now I want to loop through each list, see if the email address is already in our current data store and add ones that are not. 1: UpdateEmails(peopleWithHomeEmail); 2: UpdateEmails(peopleWithWorkEmail); A simple implementation of UpdateEmails might look like this: 1: private void UpdateEmails(IEnumerable<CSVData> list) 2: { 3: foreach (var dataItem in list) 4: { 5: Person person = Person.FindByEmailAddress(dataItem.HomeEmail); 6: if (person == null ) 7: { 8: person = new Person() 9: { 10: FirstName = dataItem.FirstName, 11: LastName = dataItem.LastName, 12: EmailAddress = dataItem.HomeEmail 13: }; 14: person.Save(); 15: } 16: } 17: } The obvious problem with this is that it always accesses the HomeEmail address field from the CSVData.  What's going to happen when I pass "peopleWithWorkEmail" to this method?  Not good. Delegates To The Rescue This is the perfect place for a delegate. We'd like to have some function that accepts a CSVData object and returns a string – either home email or work email: 1: private delegate string GetEmailAddress(CSVData data); Now we can re-write our UpdateEmails method to accept a delegate that will determine which email address we'll grab: 1: private void UpdateEmails(IEnumerable<CSVData> list, GetEmailAddress getEmail) 2: { 3: foreach (var dataItem in list) 4: { 5: Person person = Person.FindByEmailAddress(getEmail(dataItem)); 6: if (person == null ) 7: { 8: person = new Person() 9: { 10: FirstName = dataItem.FirstName, 11: LastName = dataItem.LastName, 12: EmailAddress = getEmail(dataItem) 13: }; 14: person.Save(); 15: } 16: } 17: } Thanks lambdas, we can make the calling code very clean: 1: UpdateEmails(peopleWithHomeEmail, p => p.HomeEmail); 2: UpdateEmails(peopleWithWorkEmail, p => p.WorkEmail); A very clean solution.  Except… Delegate Maintenance The only issue with this is now we have a delegate sitting around just for this simple lambda expression.  At some point in time, we may want to do some date calculations from data found inside CSVData.  If we had multiple dates to pick from (like multiple emails in this situation), we may have to create another delegate that accepts a CSVData and returns a DateTime.  What we need is a generic way of defining a method that accepts some data type(s) and returns a specific data type (note emphasis on generic !). Since this is such a common scenario, Microsoft has pre-defined a bunch of generic delegates that do exactly what we need. Getty Func-y Here's what you can use from System.Core: Func<TResult> – This delegate takes no parameters and simply returns an object of type TResult Func<T, TResult> – Just like Func<TResult>, but this one accepts a single parameter (T).  This is exactly the situation we have in our example. Microsoft also defines three other Func<> delegates – one that accepts 2 parameters, one that accepts 3 and finally, one that accepts 4.  Anything more than four and you'd have to define your own Func<> delegate. We can now get rid of our GetEmailAddress delegate and replace it with a Func<CSVData, string> (which is the exact same signature – a method that accepts a CSVData and returns a string): 1: private void UpdateEmails(IEnumerable<CSVData> list, Func<CSVData, string > getEmail) Our calling code doesn't need to change at all.  We're still using the same signature, so the C# compiler can infer the delegate...
Posted by Patrick Steele's .NET Blog
Filed under: , ,

Loops, Conversions and Lambdas

I ran across some old code today while fixing a bug and was able to simplify it quite a bit using the latest version of C# – 3.5 (as well as fix the bug!).  Changing the code reminded me of where we (.NET developers) came from and how we've gotten to where we are today. Data Conversion For the sake of argument, let's say I have a double array.  I need to use this double array in a UI routine that expects a string[] (a representation of my doubles).  Back in .NET 1.x, converting this to a string[] was pretty simple (and we're ignoring culture issues in the interest of simplicity): 1: // convert double[] to string[] 2: string [] sa = new string [data.Length]; 3: for ( int i = 0; i < data.Length; i++) 4: { 5: sa[i] = data[i].ToString(); 6: } .NET 2.0 When .NET 2.0 hit the scene, we got generics .  Since converting an array of data from one type to another was so common, Microsoft took advantage of generics and gave us Array.ConvertAll() : 1: public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] array, Converter<TInput, TOutput> converter); This takes an array of some type (TInput[]) and produces an array of a different type (TOutput).  The second parameter is a delegate that does the conversion routine.  Array.ConvertAll creates your output array and loops through your input array, calling the method pointed to by your delegate for each member to do the conversion.  A simple example would be: 1: string [] genericSA = Array.ConvertAll(data, new Converter< double , string >(DoubleToStringConverter)); 2:   3: private static string DoubleToStringConverter( double d) 4: { 5: return d.ToString(); 6: } Anonymous Methods So if we consider our first example, we took lines 2-6 and trimmed them down to one line of code (Array.ConvertAll).  But, in the process, we had to create that conversion method that contained the logic from line 5.  Microsoft decided that it would be nice to just sort of "inline" your method logic right in the code.  That's where anonymous methods come in. The second parameter to Array.ConvertAll has to be a delegate of type Converter<TInput,TOutput> – or more specifically in our case, Converter<double,string>.  Since the compiler knows this, why require the developer to type in a whole separate method along with a delegate creation statement?  With anonymous methods, we can put our conversion code right in the call to Array.ConvertAll: 1: string [] sa2 = Array.ConvertAll(data, delegate ( double d) { return d.ToString(); }); Now we're getting some pretty clean code!  Our conversion code is right there inside our call to ConvertAll (easier for reading and code reviews).  This code will compile to almost the exact same code as earlier (where we had our own method and delegate conversion) because internally, the C# compiler will actually create a separate method (anonymously!) with our "return d.ToString()" code and will also generate a new Converter<double, string> delegate to pass to the ConvertAll method.  Nice! Lambdas The final icing on the cake for this conversion is the introduction of lambdas .  Lambdas seem a little scary at first, but if you've followed along with this example, you'll see how the lambda syntax has been implemented. Like the introduction of anonymous methods, lambdas help us reduce the amount of code we write by inferring details that exist in your code.  Like our anonymous method example where the compiler will create the anonymous method and delegate call, a lambda will do the same thing.  So if we look at our conversion code above: 1: delegate ( double d) { return d.ToString(); } The C# compiler knows we need a delegate that takes a double (since we're passing in a double[]) and returns a string (the returning of a string is inferred because the anonymous method returns a string).  If it knows all that, why make the developer type in "delegate" and the type of data ("double")?  Enter lambdas.  The above code can now be reduced to the following lambda expression: 1: (d) => d.ToString() If you only have a single argument, you can skip the parenthesis and simply enter: 1: d => d.ToString() The "return" is implied.  Microsoft says the "=>" operator is read as "goes to".  So our example reads "d goes to d.ToString()".  As before, this code will compile to almost exactly the same code as our first Array.ConvertAll example using a separate method and delegate creation (and exactly the same code as when we used anonymous methods).  But this time, we let the compiler to most of the plumbing. End Result We've taken our original 1.x code of: 1: string [] sa = new string [data.Length]; 2: for ( int i = 0; i < data.Length; i++) 3: { 4: sa[i] = data[i].ToString(); 5: } And have been able to...