Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

Archive for Lambda Expressions

December 1, 2010

Using ConvertAll to Convert a Set of Values

Filed under: C#,Lambda Expressions,VB.NET @ 12:03 am

ConvertAll is one of those methods that is not used very often, but when you need it, it is very useful. It converts all of the elements of one list or array into element of another type.

In this example the user enters a list of numbers into a TextBox as a string, that list is converted to a list of integers, and the integers are averaged.

In C#:

string numbers = textBox1.Text;
if (!String.IsNullOrEmpty(numbers))
{
    var stringArray = numbers.Split(‘,’);
    int value;
    var numberArray = Array.ConvertAll(stringArray,
        s => int.TryParse(s, out value) ? value : 0);
    MessageBox.Show("Average is: " + numberArray.Average());
}

In VB:

Dim numbers As String = TextBox1.Text
If Not String.IsNullOrEmpty(numbers) Then
    Dim stringArray = numbers.Split(","c)
    Dim value As Integer
    Dim numberArray = Array.ConvertAll(stringArray, 
               Function(s) If(Integer.TryParse(s, value), value, 0))
    MessageBox.Show("Average is: " & numberArray.Average())
End If

This code resides in a Button Click event for a Windows form that contains a TextBox and a Button. The user enters a set of numbers into the TextBox, separated by commas, and clicks the button.

The code uses the Split function to split the entered string of numbers into an array using the comma as the separator.

The Array.ConvertAll method converts the resulting string array into an integer array using the Integer.TryParse method. If the value can be parsed to an integer, it uses the value. If not, it uses a zero. This protects the code from invalid user input.

Finally, it uses the Average method to calculate the numeric average of the resulting list.

Use this technique any time you have a list or array of values and need to convert them all to another type.

Enjoy!

November 19, 2010

Associating Code with a Tag using Delegates II

Filed under: C#,Lambda Expressions,VB.NET,WinForms @ 11:32 am

This prior post demonstrated how to use delegates to associated code with a Tag in a WinForms control. This post shows how to accomplish the same thing using Lambda expressions to define the delegate.

To try out this example, create a WinForms application and add a TreeView control to the form. Then add the code below.

In C#:

public delegate void DisplayMessage(string value);

private void Form1_Load(object sender, EventArgs e)
{
    var rootTreeNode = treeView1.Nodes.Add("Phones");

    var appleTreeNode = rootTreeNode.Nodes.Add("IPhone is the best");
    appleTreeNode.Tag = new DisplayMessage(
                          x => MessageBox.Show("Apple: " + x));

    var microsoftTreeNode = rootTreeNode.Nodes.Add("Win 7 Phone is the best");
    microsoftTreeNode.Tag = new DisplayMessage(
                          x => MessageBox.Show("Microsoft: " + x));

    treeView1.ExpandAll();
}

private void treeView1_DoubleClick(object sender, EventArgs e)
{
    var messageDelegate = (DisplayMessage)treeView1.SelectedNode.Tag;
    messageDelegate.Invoke(treeView1.SelectedNode.Text);
}

In VB:

Public Delegate Sub DisplayMessage(ByVal value As String)

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Dim rootTreeNode = TreeView1.Nodes.Add("Phones")

    Dim appleTreeNode = rootTreeNode.Nodes.Add("IPhone is the best")
    appleTreeNode.Tag = New DisplayMessage(
                         Sub(x) MessageBox.Show("Apple: " & x))

    Dim microsoftTreeNode = rootTreeNode.Nodes.Add("Win 7 Phone is the best")
    microsoftTreeNode.Tag = New DisplayMessage(
                        Sub(x) MessageBox.Show("Microsoft: " & x))

    TreeView1.ExpandAll()
End Sub

Private Sub TreeView1_DoubleClick(ByVal sender As Object, 
                ByVal e As EventArgs) Handles TreeView1.DoubleClick
    Dim messageDelegate = TryCast(TreeView1.SelectedNode.Tag,
                                                DisplayMessage)
    If messageDelegate IsNot Nothing Then
        messageDelegate.Invoke(TreeView1.SelectedNode.Text)
    End If
End Sub

The first line of code declares a delegate. If you have not worked with delegates before, see this prior blog post for more information.

The Load event populates the Treeview with two child nodes. A new instance of the delegate is stored in the Tag property of each node.

The delegate instance constructor defines the code to call when the user double-clicks on the node. This code is defined using a Lambda expression. If you have not worked with a Lambda expression before, see this prior blog post for more information.

The DoubleClick event then pulls the delegate from the Tag property and calls it using the Invoke method, passing the appropriate parameters.

Use this technique any time you want to define code to execute in the Tag property of a control.

Enjoy!

October 30, 2010

SelectMany: Finding in a Child List

Filed under: C#,Lambda Expressions,VB.NET @ 11:44 pm

In this prior post, I demonstrated how to find a specific item in a generic list of items. But what if the item you want to find is in a child list?

For example, a list of customers where each customer is a company that has a company name and a list of contact persons. You may want to find a customer by matching the names of the contacts.

The sample Customer and Contact classes along with the code to build the list of customers with their contacts can be found in this prior blog post.

You can use the Select extension method on IEnumerable to find within a child list.

In C#:

var foundContacts = custList.
         Select(cust=> cust.ContactList.
                Where(cn=> cn.LastName.StartsWith("B")));

foreach (var item in foundContacts)
{
     foreach (var childItem in item)
         Console.WriteLine(childItem.LastName + ", " +
                           childItem.FirstName);
}

In VB:

Dim foundContacts = custList.
        Select(Function(cust) cust.ContactList.
               Where(Function(cn) cn.LastName.StartsWith("B")))

For Each item In foundContacts
    For Each childItem In item
        Console.WriteLine(childItem.LastName & ", " &
                          childItem.FirstName)
    Next
Next

The first line of code uses a Lambda expression to find the contacts with a last name that starts with "B".

[To begin with an overview of lambda expressions, start here.]

This Select statement returns an IEnumerable<IEnumerable<Contact>>, which is a little different to loop through as shown in the looping code that outputs the results of the search.

There are several issues with this approach. First is that it does not provide any access to information about the customer. So you cannot easily determine which customer is associated with the found contact name.  Second, accessing the IEnumerable<IEnumerable<Contact>> requires double loops.

Instead of using a standard Select extension method, use the SelectMany extension method to get around both of these issues.

The following shows the SelectMany extension method using only one parameter.

In C#:

var foundContacts = custList.
        SelectMany(cust=> cust.ContactList.
            Where(cn=> cn.LastName.StartsWith("B")));

foreach (var item in foundContacts)
    Console.WriteLine(item.LastName + ", " + item.FirstName);

In VB:

Dim foundContacts = custList.
        SelectMany(Function(cust) cust.ContactList.
            Where(Function(cn) cn.LastName.StartsWith("B")))

For Each item In foundContacts
    Console.WriteLine(item.LastName & ", " & item.FirstName)
Next

The above code replaces the Select statement with SelectMany. It then returns an IEnumerable<Contact>, which is much easier to access using a single loop. However, this still does not provide access to the customer.

This code demonstrates the SelectMany extension method using two parameters.

In C#:

var foundCustomers = custList.
        SelectMany(cust=> cust.ContactList.
            Where(cn=> cn.LastName.StartsWith("B")),
                    (cust, cn)=> cust);

foreach (var item in foundCustomers)
    Console.WriteLine(item.CompanyName);

In VB:

Dim foundCustomers = custList.
        SelectMany(Function(cust) cust.ContactList.
            Where(Function(cn) cn.LastName.StartsWith("B")),
                    Function(cust, cn) cust)

For Each item In foundCustomers
    Console.WriteLine(item.CompanyName)
Next

The first parameter is the same Lambda expression from the prior two examples. The second parameter is another Lambda expressions that simply returns cust. This means the the result of this search is an IEnumerable<Customer>.

Use SelectMany any time you want to search a child list and return either the list of matching children or the list of matching parents.

Enjoy!

Sorting Lists with Null Values

Filed under: C#,Lambda Expressions,VB.NET @ 2:04 am
When working with data, you often have values that are null. For example, you may allow entry of a user’s birthday, but not require entry. So some of your rows may contain a null date. Or your application may track a customer’s last order date, which will be null if you track potential customers that have not yet made a purchase.

When you sort a list that contains null values, the null values will be first in the list as shown below:

image

But what if you want the null values at the end and not at the beginning?

image

This post demonstrates one technique for sorting your null values to the bottom of the list.

First, some prerequisite code to build the list of business objects that are bound to the grid.

In C#:

Here is the Customer class:

public class Customer { public int CustomerId { get; set; } public string LastName { get; set; } public string FirstName { get; set; } public string EmailAddress { get; set; } public DateTime? LastOrderDate { get; set; } }

And here is the one line of code to build the list of customers:

    List<Customer> custList = new List<Customer> {new Customer() { CustomerId = 1, FirstName=”Bilbo”, LastName = “Baggins”, EmailAddress = “bb@hob.me”, LastOrderDate = new DateTime(2010,9,1)}, new Customer() { CustomerId = 2, FirstName=”Frodo”, LastName = “Baggins”, EmailAddress = “fb@hob.me”, LastOrderDate = null}, new Customer() { CustomerId = 3, FirstName = “Samwise”, LastName = “Gamgee”, EmailAddress = “sg@hob.me”, LastOrderDate = new DateTime(2010, 6, 15), }, new Customer() { CustomerId = 4, FirstName = “Rosie”, LastName = “Cotton”, EmailAddress = “rc@hob.me”, LastOrderDate = new DateTime(2010, 6, 15), } };

The code to bind this list to the grid is:

var sortedList = custList.OrderBy(c => c.LastOrderDate); dataGridView1.DataSource = sortedList.ToList();

In VB:

Here is the Customer class:

Public Class Customer Public Property CustomerId As Integer Public Property LastName As String Public Property FirstName As String Public Property EmailAddress As String Public Property LastOrderDate As DateTime? End Class

And here is the one line of code to build the list of customers:

Dim custList As New List(Of Customer) From {New Customer() With {.CustomerId = 1, .FirstName = “Bilbo”, .LastName = “Baggins”, .EmailAddress = “bb@hob.me”, .LastOrderDate = New DateTime(2010, 9, 1)}, New Customer() With {.CustomerId = 2, .FirstName = “Frodo”, .LastName = “Baggins”, .EmailAddress = “fb@hob.me”, .LastOrderDate = Nothing}, New Customer() With {.CustomerId = 3, .FirstName = “Samwise”, .LastName = “Gamgee”, .EmailAddress = “sg@hob.me”, .LastOrderDate = New DateTime(2010, 6, 15)}, New Customer() With {.CustomerId = 4, .FirstName = “Rosie”, .LastName = “Cotton”, .EmailAddress = “rc@hob.me”, .LastOrderDate = New DateTime(2010, 6, 15)} }

The code to bind this list to the grid is:

Dim sortedList = custList.OrderBy(Function(c) c.LastOrderDate) DataGridView1.DataSource = sortedList.ToList()

The first set of code defines the customer business object. Note how the LastOrderDate property is defined as a Nullable structure. This allows the date to hold a null value. Both the C# and VB examples use auto-implemented properties, which provide a shortened property syntax.

See this link for more information about auto-implemented properties.

The second (very long) line of code builds the list of customers. Both the C# and VB code build the list using collection initializer syntax.

The last two lines of code first sort the list using a Lambda expression and then bind that sorted list to a DataGridView.

If you are not familiar with Lambda expressions, see an overview here.

But this example sorts in the default order, with null values first. One way to sort null values to the end is to perform a descending sort. But then all of the rows are sorted in the opposite direction.

If you want an ascending sort for all of the data, but nulls at the end, you can use multiple sorts as shown below.

In C#:

var sortedList = custList .OrderBy(c => !c.LastOrderDate.HasValue) .ThenBy(c => c.LastOrderDate); dataGridView1.DataSource = sortedList.ToList();

In VB:

Dim sortedList = custList. OrderBy(Function(c) Not c.LastOrderDate.HasValue). ThenBy(Function(c) c.LastOrderDate) DataGridView1.DataSource = sortedList.ToList()

This code first sorts by the LastOrderDate’s HasValue property. The HasValue property of the Nullable structure defines whether or not the property has a value. So if the property has a value, HasValue is true and if the property is null, HasValue is false. This code checks for “not HasValue”, so if the  property is null, the expression is true.

Since true values are sorted to the end in an ascending sort, the dates with values will be first in the sort order, then the null values.

The secondary sort (ThenBy), then sorts by the LastOrderDate. This provides the list with the rows in ascending date order, but with the null values last in the list.

Use this technique any time you need to sort a list and ensure that all null values are at the end of the resulting sorted list.

Enjoy!
May 7, 2010

LINQ: Mean, Median, and Mode

Filed under: C#,Lambda Expressions,LINQ,VB.NET @ 2:23 am

If you are doing any type of statistical analysis, you probably need to calculate mean, median and mode. There are lots of places on the Web you can find the calculations. This post is different than most in that it uses LINQ and Lambda expressions.

Mean is the statistical average of a set of numbers. This one is easy with LINQ because of the Average function.

In C#:

int[] numbers = { 4, 4, 4, 4, 3, 2, 2, 2, 1 };

double mean = numbers.Average();
Debug.WriteLine(("Mean: " + mean));

In VB:

Dim numbers() As Integer = {4, 4, 4, 4, 3, 2, 2, 2, 1}

Dim mean As Double = numbers.Average()
Debug.WriteLine("Mean: " & mean)

The result is:

Mean: 2.88888888888889

This code uses the Average extension method on the IEnumerable class to calculate the mean, or average, of the numbers.

Median is the middle number of a set of numbers. If there is an even number of entries, it is the average of the two middle numbers.

In C#:

int[] numbers = { 4, 4, 4, 4, 3, 2, 2, 2, 1 };

int numberCount = numbers.Count();
int halfIndex = numbers.Count()/2;
var sortedNumbers = numbers.OrderBy(n=>n);
double median;
if ((numberCount % 2) == 0)
{
    median = ((sortedNumbers.ElementAt(halfIndex) +
        sortedNumbers.ElementAt((halfIndex – 1)))/ 2);
} else {
    median = sortedNumbers.ElementAt(halfIndex);
}
Debug.WriteLine(("Median is: " + median));

In VB:

Dim numbers() As Integer = {4, 4, 4, 4, 3, 2, 2, 2, 1}

Dim numberCount As Integer = numbers.Count
Dim halfIndex As Integer = numbers.Count \ 2
Dim sortedNumbers = numbers.OrderBy(Function(n) n)
Dim median As Double
If (numberCount Mod 2 = 0) Then
    median = (sortedNumbers.ElementAt(halfIndex) +
       sortedNumbers.ElementAt(halfIndex – 1)) / 2
Else
    median = sortedNumbers.ElementAt(halfIndex)
End If
Debug.WriteLine("Median is: " & median)

The result is:

Median is: 3

This code first counts the numbers and divides the count by 2 to find the middle of the list. Note that the VB code uses the backslash (\) to perform an integer division where the C# code uses a forward slash (/) for the division.

It then sorts the numbers in order using the OrderBy extension method and a Lambda expression that simply orders by the numbers.

The last step is to get the element at the middle (if odd) or the average of the two middle elements (if even). The result is the median.

Mode is the number that occurs the largest number of times.

In C#:

int[] numbers = { 4, 4, 4, 4, 3, 2, 2, 2, 1 };

var mode = numbers.GroupBy(n=> n).
    OrderByDescending(g=> g.Count()).
    Select(g => g.Key).FirstOrDefault();
Debug.WriteLine(("Mode is: " + mode));

In VB:

Dim numbers() As Integer = {4, 4, 4, 4, 3, 2, 2, 2, 1}

Dim mode = numbers.GroupBy(Function(n) n).
     OrderByDescending(Function(g) g.Count).
     Select(Function(g) g.Key).FirstOrDefault
Debug.WriteLine("Mode is: " & mode)

The result is:

Mode is: 4

This code uses the GroupBy extension method on IEnumerable to group the numbers by number. It then orders them by the count and selects the first one. This provides the number that occurs the most times.

Use these techniques whenever you need to calculate the mean, median, or mode.

Enjoy!

May 5, 2010

Multi-dimensional Arrays and IEnumerable

Filed under: C#,Lambda Expressions,VB.NET @ 12:19 am

Most of the cool extension methods that you can use with Lambda expressions, such as Where, FirstOrDefault, and Zip, are extensions of the generic IEnumerable<T> class. Interestingly, the .NET multi-dimensional array does not implement the generic IEnumerable<T> class. So you don’t have direct access to any of the Linq extension methods.

The reason for this is that a single-dimension array is implemented differently from a multi-dimension array in .NET. This stackoverflow post provides more detail on this issue.

The purpose of this post is to provide a way to work with extension methods on multi-dimensions arrays. The trick is to use Cast.

This example defines a 3×3 array with three student’s scores on three exams. It then averages the scores.

In C#:

int[,] multiArray = {{70, 88, 90}, {98, 100, 96}, {88, 94, 95}};

var averageScore = multiArray.Cast<int>().Average();

In VB:

Dim multiArray(,) As Integer = {{70, 88, 90}, {98, 100, 96},
                                              {88, 94, 95}}

Dim averageScore = multiArray.Cast(Of Integer).Average

The first line of code defines the 3 x 3 array.

The second line of code uses the Cast operator to cast the array to a generic IEnumerable(Of Integer). This basically flattens the array into a single set of integers.

Once you have a generic IEnumerable, you can use its associated extension methods, including Average. The result is an average of 91.

Use this technique any time you have to work with multi-dimensional arrays.

Enjoy!

May 4, 2010

Lambda Expressions: Zip

Filed under: C#,Lambda Expressions,VB.NET @ 11:33 pm

One of the new features in C# 4 and VB 10 (.NET 4.0) is the Zip extension method on IEnumerable. This method allows you to merge two lists such as two arrays or two generic List<T>.

The Zip extension method merges the two sequences by matching up each item in one sequence with another item in another sequence.

Starting with a simple example, say you have an array of numbers 0-5. And you also have an array of strings  that contain the number names zero-five. You want to merge these into a single array in the form: number (name), e.g. 0 (zero), 1 (one), and so on.

In C#:

int[] numberArray  = {0, 1, 2, 3, 4, 5};
string[] nameArray = {"zero", "one", "two",
                 "three", "four", "five"};

var combinedArray = numberArray.Zip(nameArray,
                    (number, name) => number.ToString() + " (" + 
                                      name + ")").ToArray();
Array.ForEach(combinedArray, s=> Console.WriteLine(s));

In VB:

Dim numberArray() As Integer = {0, 1, 2, 3, 4, 5}
Dim nameArray() As String = {"zero", "one", "two",
                             "three", "four", "five"}

Dim combinedArray = numberArray.Zip(nameArray,
                    Function(number, name) number.ToString & " (" & 
                                           name & ")").ToArray
Array.ForEach(combinedArray, Sub(s) Console.WriteLine(s))

The first two lines of code declare the two arrays: one with a set of numbers and the other with a set of names.

The Zip extension method combines the two arrays. Use the Zip method on either of the arrays and set the first parameter of the Zip method to the other array. The second parameter of the Zip method is a Lambda expression that details the desired result. In this case, the merged array contains a string concatenation of the number and name.

The parameters to the Lambda expression, number and name in this example, represent a value from each of the two arrays. The first parameter maps to the array defined to the left of the Zip method and the second parameter maps to the array defined as the first parameter to the Zip method.

(For more technical detail on how the Zip method works, see Bart De Smet’s blog post.)

The last line of code uses the ForEach extension method  to display each value in the merged array to the console.

The result is:

0 (zero)
1 (one)
2 (two)
3 (three)
4 (four)
5 (five)

Instead of returning a simple array, you can return an array of anonymous types. An anonymous type is like a class that you create dynamically that has no name, hence the reason it is called an anonymous type.

Using the same arrays as in the prior example, this next example returns an array of anonymous types where each item has a number and name.

In C#:

var nums = numberArray.Zip(nameArray,
    (number, name) => new { number, name});

foreach (var item in nums)
    Console.WriteLine(item);

In VB:

Dim nums = numberArray.Zip(nameArray,
        Function(number, name) New With {number, name})

For Each item In nums
    Console.WriteLine(item)
Next

This code starts with the same two arrays, but this time it creates an anonymous type. The new keyword defines the anonymous type and the list of items in the curly braces sets the properties of the anonymous type.

The result is:

{ number = 0, name = zero }
{ number = 1, name = one }
{ number = 2, name = two }
{ number = 3, name = three }
{ number = 4, name = four }
{ number = 5, name = five }

Now consider a more business-oriented example. Say you have a list of customers (like the one in this prior post) and a list of invoices (like the one in this prior post). You want to merge the lists into a single list so you can bind it to a grid.

In C#:

var listToBind = custList.Zip(invoiceList,
        (cust, inv) => new {
            LastName = cust.LastName,
            FirstName = cust.FirstName,
            InvoiceAmount = inv.InvoiceAmount,
            InvoiceDate = inv.InvoiceDate});
dataGridView1.DataSource = listToBind.ToList();

In VB:

Dim listToBind = custList.Zip(invoiceList,
                    Function(cust, inv) New With {
                        .LastName = cust.LastName,
                        .FirstName = cust.FirstName,
                        .InvoiceAmount = inv.InvoiceAmount,
                        .InvoiceDate = inv.InvoiceDate})
DataGridView1.DataSource = listToBind.ToList

This code uses the Zip extension method to combine the customer list and the invoice list. It uses the new keyword to create  an anonymous type with properties from both lists. The result can be bound to a grid, such as the DataGridView.

The result is as follows:

image

You can use filtering and sorting to change the contents of the resulting list. For example, the following code only displays invoices with amounts > 100.

In C#:

var listToBind = custList.Zip(invoiceList,
        (cust, inv) => new {
            LastName = cust.LastName,
            FirstName = cust.FirstName,
            InvoiceAmount = inv.InvoiceAmount,
            InvoiceDate = inv.InvoiceDate}).
            Where(c => c.InvoiceAmount>100);
dataGridView1.DataSource = listToBind.ToList();

In VB:

Dim listToBind = custList.Zip(invoiceList,
                    Function(cust, inv) New With {
                        .LastName = cust.LastName,
                        .FirstName = cust.FirstName,
                        .InvoiceAmount = inv.InvoiceAmount,
                        .InvoiceDate = inv.InvoiceDate}).
                        Where(Function(c) c.InvoiceAmount > 100)
DataGridView1.DataSource = listToBind.ToList

Use the Zip extension method any time you need to combine lists or build a new list from existing lists. This is especially useful for binding to grids.

Enjoy!

Grouping and Summing with Lambda Expressions

Filed under: C#,Lambda Expressions,LINQ,VB.NET @ 12:43 am

There are often cases where you need to group on multiple properties and potentially sum on others. This post details how to group and sum on multiple properties using lambda expressions.

This example uses an Invoice class, since that provides many opportunities for summing. First, here is a basic Invoice class.

In C#:

class Invoice
{
    public DateTime InvoiceDate { get; set; }
    public int InvoiceType { get; set; }
    public decimal InvoiceAmount { get; set; }
    public int NumberOfItems { get; set; }
}

In VB:

Public Class Invoice
    Public Property InvoiceDate As DateTime
    Public Property InvoiceType As Integer
    Public Property InvoiceAmount As Decimal
    Public Property NumberOfItems As Integer
End Class

Normally, you would populate the Invoice class from data in a database or other data store. But to keep this example simple, the values for the invoices are hard-coded.

In C#:

List<Invoice> invoiceList = new List<Invoice>();

invoiceList = new List<Invoice>
        {new Invoice()
              {
                InvoiceDate=new DateTime(2010,4,30),
                InvoiceType = 1,
                InvoiceAmount = 150,
                NumberOfItems = 8},
        new Invoice()
              {
                InvoiceDate=new DateTime(2010,4,29),
                InvoiceType = 2,
                InvoiceAmount = 215,
                NumberOfItems = 7},
        new Invoice()
              {
                InvoiceDate=new DateTime(2010,4,30),
                InvoiceType = 1,
                InvoiceAmount = 50,
                NumberOfItems = 2},
        new Invoice()
              {
                InvoiceDate=new DateTime(2010,4,29),
                InvoiceType = 2,
                InvoiceAmount = 550,
                NumberOfItems = 5}};

In VB:

Dim invoiceList As List(Of Invoice)

invoiceList = New List(Of Invoice) From
            {New Invoice With
                  {
                    .InvoiceDate = New DateTime(2010, 4, 30),
                    .InvoiceType = 1,
                    .InvoiceAmount = 150,
                    .NumberOfItems = 8},
            New Invoice With
                  {
                    .InvoiceDate = New DateTime(2010, 4, 29),
                    .InvoiceType = 2,
                    .InvoiceAmount = 215,
                    .NumberOfItems = 7},
            New Invoice With
                  {
                    .InvoiceDate = New DateTime(2010, 4, 30),
                    .InvoiceType = 1,
                    .InvoiceAmount = 50,
                    .NumberOfItems = 2},
            New Invoice With
                  {
                    .InvoiceDate = New DateTime(2010, 4, 29),
                    .InvoiceType = 2,
                    .InvoiceAmount = 550,
                    .NumberOfItems = 5}}

This code creates two invoices of type 1 that are dated 4/30/2010 and two invoices of type 2 that are dated 4/29/2010.

Now for the fun part. This example groups on both the InvoiceDate and the InvoiceType properties. The totals accumulate the invoice amount and the number of items. This allows the code to provide totals based both on the date and type.

In C#:

var query = invoiceList
            .GroupBy(g => new { g.InvoiceDate,
                                g.InvoiceType })
            .Select(group => new {
                       InvoiceDate = group.Key.InvoiceDate,
                       InvoiceType = group.Key.InvoiceType,
                       TotalAmount = group.Sum(a=>a.InvoiceAmount),
                       TotalCount = group.Sum(c=>c.NumberOfItems)});

In VB:

Dim query = invoiceList.
           GroupBy(Function(g) New With {Key g.InvoiceDate, 
                                         Key g.InvoiceType}).
           Select(Function(group) New With {
              .InvoiceDate = group.Key.InvoiceDate,
              .InvoiceType = group.Key.InvoiceType,
              .TotalAmount = group.Sum(Function(a) a.InvoiceAmount),
              .TotalCount = group.Sum(Function(c) c.NumberOfItems)})

This code starts with a GroupBy clause with a Lambda expression. The Lambda expression (indicated with the => syntax in C# and the Function keyword in VB) uses the new keyword to create a new anonymous type with two properties: InvoiceDate and InvoiceType. This technique allows the code to group on both properties. More properties can be included here if you need to group on more than two properties.

Notice the Key modifier on the two properties in the Lambda expression for the VB example. This ensures that the anonymous type is immutable, providing read-only values. This is not necessary in the C# code because C# only supports immutable anonymous types.

The next part of the above code is a Select clause with another Lambda expression. The Lambda expression uses the new keyword to create an anonymous type with four properties: the invoice date and type from the grouping and the two totals. Each total uses a Sum clause with a Lambda expression defining the property to sum.

The group.Key syntax provides the key values from the grouping, which in this case are the properties from our first anonymous type.

The group.Sum syntax provides a sum on a particular property in the original list.

To view the results of the query, the code can display the items.

In C#:

foreach (var item in query)
{
    Console.WriteLine("Invoice Date: {0} ({1}) TotalAmount: {2} TotalCount: {3}",
                        item.InvoiceDate.ToShortDateString(),
                        item.InvoiceType,
                        item.TotalAmount,
                        item.TotalCount);
}

In VB:

For Each item In query
    Console.WriteLine("Invoice Date: {0} ({1}) TotalAmount: {2} TotalCount: {3}",
                        item.InvoiceDate.ToShortDateString(),
                        item.InvoiceType,
                        item.TotalAmount,
                        item.TotalCount)
Next

The result is as follows:

Invoice Date: 4/30/2010 (1) TotalAmount: 200 TotalCount: 10
Invoice Date: 4/29/2010 (2) TotalAmount: 765 TotalCount: 12

Use this technique any time you need to group or sum information in your business objects.

Enjoy!

March 30, 2010

Generating Random Letters

Filed under: C#,Lambda Expressions,VB.NET @ 9:46 pm

Someone recently posted in the forums the need to build a "Word Search" puzzle and wanted to generate a list of random letters.

.NET has all of the features you need to do this in just two lines of code.

In C#:

Random rand  = new Random();
var letter = Enumerable.Range(0, 100).Select(
        i => (char)((int)’A’ + rand.Next(0, 26))).ToList();

In VB:

Dim rand As Random = New Random()
Dim letter = Enumerable.Range(0, 100).Select( _
            Function(i) (Chr(Asc("A") + rand.Next(0, 26)))).ToList()

This code first created an instance of the Random class. It then builds the list of random letters.

The Range method on the Enumerable class defines the range of values, in this case the code generates 100 values, starting at 0. So if you need a different number of random values, this is the number you would change.

The Lambda expression uses the ASCII code for letters, adding a random value to "A" to get "A" through "Z". Finally, the code converts the set of letters to a list.

Use this technique whenever you need to generate a set of random letters.

Enjoy!

EDITED 4/1/10: Craig found a bug in the code above. rand.Next(0,25) picks a random number greater than or equal to 0 but LESS THAN 25. So to ensure that "Z" is also used, this needs to be changed to rand.Next(0,26). I corrected the code in both examples above. Thanks Craig!

March 3, 2010

Lambda Expressions: Join

Filed under: C#,Lambda Expressions,VB.NET @ 12:24 am

If you have a set of business objects that are related to another set of business objects by a foreign key, you can use the Join method in a Lambda expression to join the two sets of business objects into one. You can then use the resulting set as a single list. This is useful when you need to perform operations on a single  list, like for binding.

Say you have a Customer class and a Contact class. The Customer class defines your set of customers. The Contact class tracks the contacts (email, text, or phone messages) from those customers. So the Contact class has a CustomerId foreign key that maps the message to the associated customer.

Here is the Customer class.

In C#:

public class Customer
{
    public int CustomerId { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string EmailAddress { get; set; }
}

In VB 10 (VS 2010):

Public Class Customer
    Public Property CustomerId As Integer
    Public Property FirstName() As String
    Public Property LastName() As String
    Public Property EmailAddress() As String
End Class

In VB 9 (VS 2008):

Public Class Customer

    Private _CustomerId As Integer
    Public Property CustomerId() As Integer
        Get
            Return _CustomerId
        End Get
        Set(ByVal value As Integer)
            _CustomerId = value
        End Set
    End Property

    Private _FirstName As String
    Public Property FirstName() As String
        Get
            Return _FirstName
        End Get
        Set(ByVal value As String)
            _FirstName = value
        End Set
    End Property

    Private _LastName As String   
    Public Property LastName() As String
        Get
            Return _LastName
        End Get
        Set(ByVal value As String)
            _LastName = value
        End Set
    End Property

    Private _EmailAddress As String
    Public Property EmailAddress () As String
        Get
            Return _EmailAddress
        End Get
        Set(ByVal value As String)
            _EmailAddress = value
        End Set
    End Property
End Class

And the Contact class:

In C#:

public class Contact
{
    public int ContactId { get; set; }
    public int CustomerId { get; set; }
    public string MessageText { get; set; }
}

In VB 10 (VS 2010):

Public Class Contact
    Public Property ContactId As Integer
    Public Property CustomerId() As Integer
    Public Property MessageText() As String
End Class

In VB 9 (VS 2008):

Public Class Contact
    Private _ContactId As Integer
    Public Property ContactId() As Integer
        Get
            Return _ContactId
        End Get
        Set(ByVal value As Integer)
            _ContactId = value
        End Set
    End Property
    Private _CustomerId As Integer
    Public Property CustomerId() As Integer
        Get
            Return _CustomerId
        End Get
        Set(ByVal value As Integer)
            _CustomerId = value
        End Set
    End Property
    Private _MessageText As String
    Public Property MessageText() As String
        Get
            Return _MessageText
        End Get
        Set(ByVal value As String)
            _MessageText = value
        End Set
    End Property
End Class

Then you populate the list of business objects, probably from a table. In this example the lists are hard-coded so you don’t have to hook up a database.

In C#:

List<Customer> custList = new List<Customer>
                    {new Customer()
                          { CustomerId = 1,
                            FirstName="Bilbo",
                            LastName = "Baggins",
                            EmailAddress = "bb@hob.me"},
                    new Customer()
                          { CustomerId = 2,
                            FirstName="Frodo",
                            LastName = "Baggins",
                            EmailAddress = "fb@hob.me"},
                    new Customer()
                          { CustomerId = 3,
                            FirstName="Samwise",
                            LastName = "Gamgee",
                            EmailAddress = "sg@hob.me"},
                    new Customer()
                          { CustomerId = 4,
                            FirstName="Rosie",
                            LastName = "Cotton",
                            EmailAddress = "
rc@hob.me"}};

List<Contact> contactList = new List<Contact>
        {new Contact()
              { ContactId = 1,
                CustomerId = 1, 
     MessageText="Please provide me with the status of my order."},
         new Contact()
              { ContactId = 2,
                CustomerId = 1,
                MessageText="Can I get the order by Friday?"},
         new Contact()
              { ContactId = 3,
                CustomerId = 2,
                MessageText="Follow up on Order # 2355"}};

In VB:

Dim custList As New List(Of Customer)
custList.Add(New Customer With {.CustomerId = 1, _
                                .LastName = "Baggins", _
                                .FirstName = "Bilbo", _
                                .EmailAddress="
bb@hob.me"})
custList.Add(New Customer With {.CustomerId = 2, _
                                .LastName = "Baggins", _
                                .FirstName = "Frodo", _
                                .EmailAddress = "
fb@hob.me"})
custList.Add(New Customer With {.CustomerId = 3, _
                                .LastName = "Gamgee", _
                                .FirstName = "Samwise", _
                                .EmailAddress = "
sg@hob.me"})
custList.Add(New Customer With {.CustomerId = 4, _
                                .LastName = "Cotton", _
                                .FirstName = "Rosie", _
                                .EmailAddress = "
rc@hob.me"})

Dim contactList As New List(Of Contact)
contactList.Add(New Contact With _
                  {.ContactId = 1, _
                    .CustomerId = 1, _
   .MessageText = "Please provide me with the status of my order."})
contactList.Add(New Contact With _
                  {.ContactId = 2, _
                    .CustomerId = 1, _
                    .MessageText = "Can I get the order by Friday?"})
contactList.Add(New Contact With _
                  {.ContactId = 3, _
                    .CustomerId = 2, _
                    .MessageText = "Follow up on Order # 2355"})

To perform the join, use the Join method from the Enumerable class:

In C#:

var contactJoin = contactList.Join(custList,
                    msg => msg.CustomerId,
                    cust => cust.CustomerId,
                    (msg, cust ) =>
                        new {LastName = cust.LastName,
                            FirstName = cust.FirstName,
                            Message = msg.MessageText });

In VB:

Dim contactJoin = contactList.Join(custList, _
               Function(msg) msg.CustomerId, _
               Function(cust) cust.CustomerId, _
               Function(msg, cust) _
                    New With {.LastName = cust.LastName, _
                        .FirstName = cust.FirstName, _
                        .Message = msg.MessageText})

In this example, the desired result is a list of contact messages but with the CustomerId replaced with the first and last name of the customer.

Notice in the above code  that the Join method is used on the contactList. This ensures that all contact messages are in the list along with their matching customers. This is called the outer list.

The first parameter to the Join method is the inner list. This is the list joined to the first (outer) list. In this case, it is custList to join the appropriate customers to their contact messages.

The joining is done using keys. So the next two parameters define the two keys. In this example, the name of the keys are the same: CustomerId.

So, the second parameter of the Join method defines the outer list’s key. In this case it is the key in Contact class that is used for the join.

The third parameter of the Join method defines the inner list’s key. In this example, it is the key in the Customer class that is used for the join.

The fourth parameter defines the values to return. In this example, the code is creating an anonymous type. The anonymous type has the contact message along with the customer’s last and first name.

You can then bind the result to a DataGridView or other control as follows.

In C#:

CustomersDataGridView.DataSource = contactJoin.ToList();

In VB:

CustomersDataGridView.DataSource = contactJoin.ToList()

And the result appears as shown below.

image

Use this technique any time you want to join two related lists.

Enjoy!

EDITED 5/2/2010: Added the VB 10 code to demonstrate the new auto-implemented properties in VB 10.

Next Page »

© 2014 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs