Chapter 16. Using LINQ

In some of the previous chapters, we illustrated how to use LINQ to perform queries on different types of data. Chapter 2 showed how to query collections and arrays, Chapter 6 to query XML trees, and Chapter 9 to query databases. This chapter shows you how to build on those simple examples to exploit the full flexibility of LINQ.

One of the best features of LINQ is that you can perform the same kinds of queries whatever the data source is. Each of the recipes in this chapter uses an array or a collection as the data source, but the same techniques can be applied equally to XML or databases. The recipes in this chapter are all self-contained and illustrate different LINQ features—but part of the allure of LINQ is that you will be able to combine these techniques to create complex and powerful queries. The recipes in this chapter describe how to perform the following tasks:

Perform a Simple LINQ Query

Problem

You need to select all items from a collection, database, or XML document.

Solution

Use the from and select keywords.

How It Works

The most basic LINQ query selects all of the items contained in a data source. The most powerful aspect of LINQ is that you can apply the same query approach to any data source and get consistent, predictable results. Microsoft has embedded LINQ support throughout the .NET Framework so that you can use arrays, collections, XML documents and databases in the same way.

To select all of the items in a data source is a simple two-step process

  1. Start a new LINQ query using the from keyword, providing an element variable name that you will use to refer to elements that LINQ finds (for example, frome indatasource).

  2. Indicate what will be added to the result set from each matching element using the select keyword.

For the basic "select all" query used in this recipe, we simply define the element variable name in the first step and use it as the basis for the second step, as follows:

IEnumerable<myType> myEnum = from e in datasource select e;

The type that you use for the datasource reference must implement the System.Collections.Generic.IEnumerable<> interface. If you are using an array or a generic collection, then you can simply use the references to your instance as the data source because arrays and all standard generic collections implement IEnumerable<>, as follows:

IEnumerable<myType> myEnum = from e in myarray select e;
IEnumerable<myType> myEnum = from e in mycollection select e;

If you are using an XML tree, you can get an IEnumerable from the root XElement by calling the Elements method; and for a DataTable, you can get an IEnumerable by calling the AsEnumerable method, as follows:

IEnumerable<XElement> myEnum = from e in root.Elements() select e;
IEnumerable<DataRow> myEnum = from e in table.AsEnumerable() select e;

Notice that the generic type of the result is dependent on the data source. For an array or collection, the result will be an IEnumerable of the data contained in the array—string for string[] and IList<string>, for example. LINQ queries of DataTables return an IEnumerable<DataRow>, and queries of an XML tree return IEnumerable<XElement>.

If you want to select a value contained within a data source element (for example, if myType had a property called Name that returned a string), then you simply specify the value you want after the select keyword—for example:

IEnumerable<string> myEnum = from e in datasource select e.Name;

Notice that the generic type of the result has changed—we are querying a data source that contains myType instances, but selecting a string property—therefore, the result is an IEnumerable<string>.

IEnumerable<> can be used with a foreach loop to enumerate the results of a query, but because LINQ queries return instances of IEnumerable<> and LINQ data sources must implement IEnumerable<>, you can also use the result of one query as the data source for another.

The Code

The following example performs a basic LINQ query on a string array, a collection, an XML tree, and a DataTable, and prints out the results in each case:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Data;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_01
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Using an array source");
            // Create the source.
            string[] array = createArray();
            // Perform the query.
            IEnumerable<string> arrayEnum = from e in array select e;
            // Write out the elements.
            foreach (string str in arrayEnum)
            {
                Console.WriteLine("Element {0}", str);
            }

            Console.WriteLine("
Using a collection source");
            // Create the source.
            ICollection<string> collection = createCollection();
            // Perform the query.
            IEnumerable<string> collEnum = from e in collection select e;
            // Write out the elements.
            foreach (string str in collEnum)
            {
                Console.WriteLine("Element {0}", str);
            }

            Console.WriteLine("
Using an xml source");
            // Create the source.
            XElement xmltree = createXML();
// Perform the query.
            IEnumerable<XElement> xmlEnum = from e in xmltree.Elements() select e;
            // Write out the elements.
            foreach (string str in xmlEnum)
            {
                Console.WriteLine("Element {0}", str);
            }

            Console.WriteLine("
Using a data table source");
            // Create the source.
            DataTable table = createDataTable();

            // Perform the query.
            IEnumerable<string> dtEnum = from e in table.AsEnumerable()
                select e.Field<string>(0);
            // Write out the elements.
            foreach (string str in dtEnum)
            {
                Console.WriteLine("Element {0}", str);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static string[] createArray()
        {
            return new string[] { "apple", "orange", "grape", "fig",
                "plum", "banana", "cherry" };
        }

        static IList<string> createCollection()
        {
            return new List<string>() { "apple", "orange", "grape", "fig",
                "plum", "banana", "cherry" };
        }

        static XElement createXML()
        {
            return new XElement("fruit",
                new XElement("name", "apple"),
                new XElement("name", "orange"),
                new XElement("name", "grape"),
                new XElement("name", "fig"),
                new XElement("name", "plum"),
                new XElement("name", "banana"),
                new XElement("name", "cherry")
            );
        }
static DataTable createDataTable()
        {
            DataTable table = new DataTable();
            table.Columns.Add("name", typeof(string));
            string[] fruit = { "apple", "orange", "grape", "fig", "plum",
                "banana", "cherry" };
            foreach (string name in fruit)
            {
                table.Rows.Add(name);
            }
            return table;
        }
    }
}

Running the example produces the following result:

Using an array source


Element apple


Element orange


Element grape


Element fig


Element plum


Element banana


Element cherry





Using a collection source


Element apple


Element orange


Element grape


Element fig


Element plum


Element banana
Element cherry





Using an xml source


Element apple


Element orange


Element grape


Element fig


Element plum


Element banana


Element cherry





Using a data table source


Element apple


Element orange


Element grape


Element fig


Element plum


Element banana


Element cherry



Main method complete. Press Enter

Filter Items from a Data Source

Problem

You need to filter the contents of a LINQ data source to select specific items.

Solution

Use the where keyword.

How It Works

Using the where keyword in conjunction with the basic LINQ query covered in recipe 16-1 allows you to specify criteria that will be used to filter the contents of a data source. You supply an expression that will be evaluated for each element in the data source— an element will be included in the results if your expression returns true and excluded if your expression returns false. You can use the element variable declared with the from keyword to refer to the current element.

For example, the following fragment uses the element variable to filter only string elements whose first character is t:

string[] array = { "one", "two", "three", "four" };
IEnumerable<string> result = from e in array where e[0] == 't' select e;

You can make your filter expressions as complex as required and also call methods that return a bool. A LINQ query can have multiple filters, such that the two LINQ queries in the following fragment are equivalent:

string[] array = { "one", "two", "three", "four" };
IEnumerable<string> result1
    = from e in array where e[0] == 't' where e[1] == 'w' select e;
IEnumerable<string> result2
    = from e in array where e[0] == 't' && e[1] == 'w' select e;

The Code

The following example creates a collection of a type Fruit and then filters the data using the LINQ where operator using a string comparison and an arithmetic operator:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Xml.Linq;
namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_02
    {
        static void Main(string[] args)
        {
            // Create the data.
            IList<Fruit> datasource = createData();

            // Filter based on a single characteristic.
            IEnumerable<string> result1 = from e in datasource
                                                where e.Color ==
 "green" select e.Name;
            Console.WriteLine("Filter for green fruit");
            foreach (string str in result1)
            {
                Console.WriteLine("Fruit {0}", str);
            }

            // Filter based using > operator.
            IEnumerable<Fruit> result2 = from e in datasource
                                         where e.ShelfLife > 5 select e;
            Console.WriteLine("
Filter for life > 5 days");
            foreach (Fruit fruit in result2)
            {
                Console.WriteLine("Fruit {0}", fruit.Name);
            }

            // Filter using two characteristics.
            IEnumerable<string> result3 = from e in datasource
                                                where e.Color == "green"
                                                      && e.ShelfLife > 5
                                                select e.Name;
            Console.WriteLine("
Filter for green fruit and life > 5 days");
            foreach (string str in result3)
            {
                Console.WriteLine("Fruit {0}", str);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static IList<Fruit> createData()
        {
            return new List<Fruit>()
            {
                new Fruit("apple", "green", 7),
                new Fruit("orange", "orange", 10),
                new Fruit("grape", "green", 4),
                new Fruit("fig", "brown", 12),
new Fruit("plum", "red", 2),
                new Fruit("banana", "yellow", 10),
                new Fruit("cherry", "red", 7)
            };
        }
    }
    class Fruit
    {
        public Fruit(string namearg, string colorarg, int lifearg)
        {
            Name = namearg;
            Color = colorarg;
            ShelfLife = lifearg;
        }
        public string Name { get; set;}
        public string Color { get; set;}
        public int ShelfLife { get; set;}
    }
}

Filter a Data Source by Type

Problem

You need to select all of the elements in a data source that are of a given type.

Solution

Use the LINQ OfType extension method.

How It Works

C# has keywords for many LINQ features, but they are mappings to extension methods in the System.Linq namespace—the keywords exist to simplify your code. See recipe 13-15 for a recipe to create and use an extension method. Keywords do not exist for all of the LINQ functions—some features are only available using extension methods directly. In order to filter a data source for all objects of a given type, you call the OfType<> method, specifying the type that you are looking for, as the following code fragment shows:

IEnumerable <string> stringData = mixedData.OfType<string>();

The fragment filters the data source for all string instances and will omit any other type from the results. Notice that the result of calling OfType<> is an IEnumeration<>, which can be used as the data source for a further LINQ query, as shown by the following fragment, which filters a data source for all string instances and then filters the results for strings with the first character of c:

IEnumerable<string> stringData = from e in mixedData.OfType<string>()
                                 where e[0] == 'c' select e;

Note

You must import the System.Linq namespace before you can use the LINQ extension methods with the using System.Linq statement.

The Code

The following example creates a collection of mixed.

types and then filters the elements using the OfType<> extension method:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_03
    {
        static void Main(string[] args)
        {
            IList<object> mixedData = createData();
            IEnumerable <string> stringData = mixedData.OfType<string>();
            foreach (string str in stringData)
            {
                Console.WriteLine(str);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static IList<object> createData()
        {
            return new List<object>()
            {
                "this is a string",
                23,
                9.2,
                "this is another string"
            };
        }
    }
}

Filter Ranges of Elements

Problem

You need to apply a LINQ query to part of a data source.

Solution

Use the Skip<>, Take<>, and Range<> extension methods.

How It Works

The Skip<> extension method omits the specified number of elements, starting at the beginning of the set of elements in the data source, and includes the remaining elements. The Take<> extension method does the opposite—it includes the specified number of elements and omits the rest. As with all of the LINQ extension methods, you must supply a generic type annotation when calling the method—this determines the type of IEnumeration<> that will be returned. The Range<> extension method takes a start index and a count as method parameters and returns a subset of the elements in the data source.

Skip<>, Take<>, and Range<> all return IEnumeration<>, so the results from these methods can be used either to enumerate the results or as the data source for another LINQ query.

The Code

The following example creates a string array and uses it as the data source for Take and Skip filters:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_04
    {
        static void Main(string[] args)
        {
            string[] array = { "one", "two", "three", "four", "five" };

            IEnumerable<string> skipresult
                = from e in array.Skip<string>(2) select e;
            foreach (string str in skipresult)
            {
                Console.WriteLine("Result from skip filter: {0}", str);
            }
IEnumerable<string> takeresult
                = from e in array.Take<string>(2) select e;
            foreach (string str in takeresult)
            {
                Console.WriteLine("Result from take filter: {0}", str);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();

        }
    }
}

Running the example program gives the following results:

Result from skip filter: three

Result from skip filter: four

Result from skip filter: five

Result from take filter: one

Result from take filter: two



Main method complete. Press Enter

Select Multiple Member Values

Problem

You need to select the values returned by more than one member of a data element.

Solution

Use the new keyword in your select statement to create an anonymous type.

How It Works

If you want to create a LINQ result that contains the values from more than one member of a data element, you can use the new keyword after the select keyword to create an anonymous type. An anonymous type doesn't have a name (hence "anonymous") and is made up of just the values that you specify.

You reference the result from the query using the special var type, as shown in the example code.

The Code

The following example creates a collection of the Fruit type and then performs a LINQ query that returns an anonymous type containing the Name and Color properties from each Fruit element:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_05
    {
        static void Main(string[] args)
        {
            IList<Fruit> sourcedata = createData();
            var result = from e in sourcedata
                         select new
                         {
                             e.Name,
                             e.Color
                         };
            foreach (var element in result)
            {
                Console.WriteLine("Result: {0} {1}", element.Name, element.Color);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static IList<Fruit> createData()
        {
            return new List<Fruit>()
            {
                new Fruit("apple", "green", 7),
                new Fruit("orange", "orange", 10),
                new Fruit("grape", "green", 4),
                new Fruit("fig", "brown", 12),
                new Fruit("plum", "red", 2),
new Fruit("banana", "yellow", 10),
                new Fruit("cherry", "red", 7)
            };
        }
    }
    class Fruit
    {
        public Fruit(string namearg, string colorarg, int lifearg)
        {
            Name = namearg;
            Color = colorarg;
            ShelfLife = lifearg;
        }
        public string Name { get; set; }
        public string Color { get; set; }
        public int ShelfLife { get; set; }
    }
}

Filter and Select from Multiple Data Sources

Problem

You need to create an anonymous type that contains values from multiple data sources with common keys.

Solution

Use the join...in...on...equals... keyword sequence.

How It Works

If you have two data sources that share a common key, you can combine them in a LINQ query using the join...in...on...equals... keywords. The following fragment demonstrates how to do this:

from e in firstDataSource join f in secondDataSource
    on e.CommonKey equals f.CommonKey

LINQ will arrange the data so that your filter and select statements are called once per common key. You can refer to the individual elements using the variable names you have defined—in the fragment, we have used e and f. You can join as many data sources as you wish in a LINQ query, as long as they share a common key.

The Code

The following example creates two data sources that share a common key, and uses the join keyword to combine them in a LINQ query in order to create an anonymous result type that contains elements from both data sources:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_6
    {
        static void Main(string[] args)
        {
            // Create the data sources.
            IList<FruitColor> colorsource = createColorData();
            IList<FruitShelfLife> shelflifesource = createShelfLifeData();

            // Perform the LINQ query with a join.
            var result = from e in colorsource
                         join f in shelflifesource on e.Name equals f.Name
                         where e.Color == "green"
                         select new
                         {
                             e.Name,
                             e.Color,
                             f.Life
                         };

            // Write out the results.
            foreach (var element in result)
            {
                Console.WriteLine("Name: {0}, Color: {1}, Shelf Life: {2} days",
                    element.Name, element.Color, element.Life);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static IList<FruitColor> createColorData()
        {
            return new List<FruitColor>()
            {
                new FruitColor("apple", "green"),
                new FruitColor("orange", "orange"),
                new FruitColor("grape", "green"),
new FruitColor("fig", "brown"),
                new FruitColor("plum", "red"),
                new FruitColor("banana", "yellow"),
                new FruitColor("cherry", "red")
            };
        }

        static IList<FruitShelfLife> createShelfLifeData()
        {
            return new List<FruitShelfLife>()
            {
                new FruitShelfLife("apple", 7),
                new FruitShelfLife("orange", 10),
                new FruitShelfLife("grape", 4),
                new FruitShelfLife("fig", 12),
                new FruitShelfLife("plum", 2),
                new FruitShelfLife("banana", 10),
                new FruitShelfLife("cherry",  7)
            };
        }
    }

    class FruitColor
    {
        public FruitColor(string namearg, string colorarg)
        {
            Name = namearg;
            Color = colorarg;
        }
        public string Name { get; set; }
        public string Color { get; set; }
    }

    class FruitShelfLife
    {
        public FruitShelfLife(string namearg, int lifearg)
        {
            Name = namearg;
            Life = lifearg;
        }
        public string Name { get; set; }
        public int Life{ get; set; }
    }
}

Running the example gives the following results:

Name: apple Color green Shelf Life: 7 days

Name: grape Color green Shelf Life: 4 days



Main method complete. Press Enter

Use Permutations of Data Sources

Problem

You need to enumerate all permutations of two or more data sources.

Solution

Include more than one from statement in your LINQ query.

How It Works

You can enumerate through the permutations of multiple data sources by using more than one from keyword in your LINQ query. The query will be applied to every permutation of every element in each data source. The following fragment illustrates a query that uses from twice:

string[] datasource1 = { "apple", "orange",};
int[]    datasource2 = { 21, 42 };

var result = from e in datasource1
   from f in datasource2
   select new
   {
      e,
      f
   };

The select part of the query (and any filters that we might have applied) will be called for every combination of element from the two data sources—apple and 21, apple and 42, orange and 21, and orange and 42.

The Code

The following example creates two arrays and uses them as data sources for a LINQ query with multiple from keywords. The result is an anonymous type containing the elements from both sources, and each element in the result is printed to the console.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_7
    {
        static void Main(string[] args)
        {
            // Create the data sources.
            string[] datasource1 = { "apple", "orange", "cherry", "pear" };
            int[]    datasource2 = { 21, 42, 37 };

            // Perform the LINQ query.
            var result = from e in datasource1
                         from f in datasource2
                         select new
                         {
                             e,
                             f
                         };

            // Print the results.
            foreach (var element in result)
            {
                Console.WriteLine("{0}, {1}", element.e, element.f);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Running the program produces the following results:

apple, 21

apple, 42

apple, 37

orange, 21

orange, 42

orange, 37

cherry, 21

cherry, 42

cherry, 37

pear, 21

pear, 42

pear, 37



Main method complete. Press Enter

Concatenate Data Sources

Problem

You need to combine one or more data sources.

Solution

Use the Concat<> extension method to combine multiple sources into a sequence that LINQ will process as a single data source.

How It Works

The Concat<> extension method returns an IEnumeration<> containing the element in the data source on which you call the method and the elements in the data source you pass as a method parameter. The type annotation you pass to the Concat<> method must match the element types in the data sources.

The Code

The following example concatenates two arrays of strings to form a single data source for a LINQ query:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_08
    {
        static void Main(string[] args)
        {
            // Create the data sources.
            string[] datasource1 = { "apple", "orange", "cherry", "pear" };
            string[] datasource2 = { "banana", "kiwi", "fig" };

            // Perform the LINQ query.
            IEnumerable<string> result
                = from e in datasource1.Concat<string>(datasource2)
                select e;

            // Print the results.
            foreach (string element in result)
            {
                Console.WriteLine(element);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Running the example produces the following result:

apple

orange

cherry

pear

banana

kiwi

fig



Main method complete. Press Enter

Group Result Elements by Attribute

Problem

You need to order the result of a LINQ query so that elements that share a common attribute are grouped together.

Solution

Use the group...by... keywords.

How It Works

The group...by... keywords allow you to create a result where elements that share a member value are grouped together. Using group...by... in a query results in a fragment such as this:

IEnumerable<IGrouping<T1, T2>> result
    = from e in datasource group e.FirstMember by e.SecondMember;

The result from this query in an instance of System.Linq.IGrouping<T1, T2>, where T1 is the type of e.SecondMember and T2 is the type of e.FirstMember. All of the elements in the data source that have the same value of e.SecondMember will appear in the same IGrouping<>, and there will be an instance of IGrouping<> contained in the IEnumeration<> for each distinct value of e.SecondMember that LINQ finds in the data source. The easiest way to understand these keywords is to review and run the example program that follows.

You can get the value that the elements contained in an IGrouping<> share by calling the Key property. IGrouping<> extends IEnumerable<>, so you can enumerate the values of a group using a foreach loop or use an IGrouping<> as the data source for a LINQ query.

You can access each individual group as it is created by LINQ using the order...by...into... keywords. The addition of into allows you to define a variable that will contain the IGrouping<> instance—see the example program for this recipe to see an example of using into to create an anonymous type.

The Code

The following example uses a collection of the type Fruit as the data source for two LINQ queries. The first uses a standard group...by... format to create an IEnumerable<IGrouping<string, Fruit>> result, which is then enumerated by group and the elements in each group. The second query uses group...by...into... in order to create an anonymous type containing the Key value of the group and the set of matching Fruit instances, which are then printed out.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_09
    {
        static void Main(string[] args)
        {
            // Create the data source.
            IList<Fruit> datasource = createData();

            Console.WriteLine("Perfoming group...by... query");
            // Perform a query with a basic grouping.
            IEnumerable<IGrouping<string, Fruit>> result =
                from e in datasource group e by e.Color;

            foreach (IGrouping<string, Fruit> group in result)
            {
                Console.WriteLine("
Start of group for {0}", group.Key);
                foreach (Fruit fruit in group)
                {
                    Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                        fruit.Name, fruit.Color, fruit.ShelfLife);
                }
            }
Console.WriteLine("

Perfoming group...by...into query");
            // Use the group...by...into... keywords.
            var result2 = from e in datasource
                         group e by e.Color into g
                         select new
                         {
                            Color = g.Key,
                            Fruits = g
                         };

            foreach (var element in result2)
            {
                Console.WriteLine("
Element for color {0}", element.Color);
                foreach (var fruit in element.Fruits)
                {
                    Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                        fruit.Name, fruit.Color, fruit.ShelfLife);
                }
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static IList<Fruit> createData()
        {
            return new List<Fruit>()
            {
                new Fruit("apple", "green", 7),
                new Fruit("orange", "orange", 10),
                new Fruit("grape", "green", 4),
                new Fruit("fig", "brown", 12),
                new Fruit("plum", "red", 2),
                new Fruit("banana", "yellow", 10),
                new Fruit("cherry", "red", 7)
            };
        }
    }
    class Fruit
    {
        public Fruit(string namearg, string colorarg, int lifearg)
        {
            Name = namearg;
            Color = colorarg;
            ShelfLife = lifearg;
        }
public string Name { get; set; }
        public string Color { get; set; }
        public int ShelfLife { get; set; }
    }
}

Running the example program produces the following results:

Perfoming order...by... query


Start of group for green

Name: apple Color: green Shelf Life: 7 days.

Name: grape Color: green Shelf Life: 4 days.


Start of group for orange

Name: orange Color: orange Shelf Life: 10 days.


Start of group for brown

Name: fig Color: brown Shelf Life: 12 days.


Start of group for red

Name: plum Color: red Shelf Life: 2 days.

Name: cherry Color: red Shelf Life: 7 days.
Start of group for yellow

Name: banana Color: yellow Shelf Life: 10 days.


Perfoming order...by...into query


Element for color green

Name: apple Color: green Shelf Life: 7 days.

Name: grape Color: green Shelf Life: 4 days.


Element for color orange

Name: orange Color: orange Shelf Life: 10 days.


Element for color brown

Name: fig Color: brown Shelf Life: 12 days.


Element for color red

Name: plum Color: red Shelf Life: 2 days.

Name: cherry Color: red Shelf Life: 7 days.


Element for color yellow

Name: banana Color: yellow Shelf Life: 10 days.


Main method complete. Press Enter

Sort Query Results

Problem

You need to sort the results of a LINQ query.

Solution

Use the orderby keyword.

How It Works

The orderby keyword sorts the result elements of a LINQ query by the member you specify. You can sort on several members by using the orderby keyword more than once—see the example code for this recipe for an illustration. By default, LINQ will sort the elements in ascending order (the smallest value will come first in the results)—you can use the descending keyword after the member you want to use for sorting to get the reverse effect.

The Code

The following example creates a collection containing the Fruit type and uses it as the basis for a LINQ query that orders the results by the Color property in descending order and then the Name property in ascending order.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_10
    {
        static void Main(string[] args)
        {
            // Create the data source.
            IList<Fruit> datasource = createData();

            IEnumerable<Fruit> result = from e in datasource
                                              orderby e.Name
                                              orderby e.Color descending
                                              select e;

            foreach (Fruit fruit in result)
            {
Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                    fruit.Name, fruit.Color, fruit.ShelfLife);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static IList<Fruit> createData()
        {
            return new List<Fruit>()
            {
                new Fruit("apple", "red", 7),
                new Fruit("apple", "green", 7),
                new Fruit("orange", "orange", 10),
                new Fruit("grape", "green", 4),
                new Fruit("fig", "brown", 12),
                new Fruit("plum", "red", 2),
                new Fruit("banana", "yellow", 10),
                new Fruit("cherry", "red", 7)
            };
        }
    }
    class Fruit
    {
        public Fruit(string namearg, string colorarg, int lifearg)
        {
            Name = namearg;
            Color = colorarg;
            ShelfLife = lifearg;
        }
        public string Name { get; set; }
        public string Color { get; set; }
        public int ShelfLife { get; set; }
    }
}

Running the program gives the following results:

Name: apple Color: red Shelf Life: 7 days.

Name: apple Color: green Shelf Life: 7 days.

Name: banana Color: yellow Shelf Life: 10 days.

Name: cherry Color: red Shelf Life: 7 days.

Name: fig Color: brown Shelf Life: 12 days.
Name: grape Color: green Shelf Life: 4 days.

Name: orange Color: orange Shelf Life: 10 days.

Name: plum Color: red Shelf Life: 2 days.


Main method complete. Press Enter

Compare Data Sources

Problem

You need to determine whether two data sources contain the same elements.

The Solution

Use the SequenceEquals<> extension method.

How It Works

The SequenceEquals<> extension method compares two data sources and returns true if both data sources contain the same number of elements and the individual elements in each position in each data source are the same. You can specify your own code to assess element equality by implementing the System.Collections.Generic.IEqualityComparer<> interface and supplying an instance of the implementation as an argument to SequenceEquals<>.

The Code

The following example creates four data sources. The first contains a list of names of fruit. The second contains the same names in the same order. The third contains the same names in a different order, and the last contains different names, but with the same first letters as the names in the first list. Comparisons are then performed using the default IEqualityComparer and a custom IEqualityComparer that treats strings with the same first character as being equal.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_11
    {
        static void Main(string[] args)
        {
            // Create the first data source.
            string[] ds1 = { "apple", "cherry", "pear" };
            // Create a data source with the same elements
            // in the same order.
            string[] ds2 = { "apple", "cherry", "pear" };
            // Create a data source with the
            // same elements in a different order.
            string[] ds3 = { "pear", "cherry", "apple" };
            // Create a data source with different elements.
            string[] ds4 = { "apricot", "cranberry", "plum" };

            // Perform the comparisons.
            Console.WriteLine("Using standard comparer");
            Console.WriteLine("DS1 == DS2? {0}", ds1.SequenceEqual(ds2));
            Console.WriteLine("DS1 == DS3? {0}", ds1.SequenceEqual(ds3));
            Console.WriteLine("DS1 == DS4? {0}", ds1.SequenceEqual(ds4));

            // Create the custom comparer.
            MyComparer comparer = new MyComparer();

            Console.WriteLine("
Using custom comparer");
            Console.WriteLine("DS1 == DS2? {0}", ds1.SequenceEqual(ds2, comparer));
            Console.WriteLine("DS1 == DS3? {0}", ds1.SequenceEqual(ds3, comparer));
            Console.WriteLine("DS1 == DS4? {0}", ds1.SequenceEqual(ds4, comparer));

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
    class MyComparer : IEqualityComparer<string>
    {
        public bool Equals(string first, string second)
        {
            return first[0] == second[0];
        }

        public int GetHashCode(string str)
        {
            return str[0].GetHashCode();
        }
    }
}

Running the program gives the following results:

Using standard comparer

DS1 == DS2? True

DS1 == DS3? False

DS1 == DS4? False



Using custom comparer

DS1 == DS2? True

DS1 == DS3? False

DS1 == DS4? True



Main method complete. Press Enter

Aggregate Data Sources

Problem

You need to aggregate the values in a data source.

Solution

Use the Average<>, Count<>, Max<>, Min<>, or Sum<> extension methods for standard aggregations, or the Aggregate<> extension method to perform a custom aggregation.

How It Works

The standard aggregation extension methods process the elements in a data source to perform useful calculations. Average<> calculates the mean value, Count<> returns the number of elements in the data source, Min<> and Max<> return the smallest and largest elements, and Sum<> totals the elements.

You can perform custom aggregation operations using the Aggregate<> method. The example code demonstrates two custom aggregation operations. The expression receives two arguments—the first is the aggregate value so far and the second is the current element to process. The parameters and return value are of the same type as the data source type—that is, if you are aggregating an IEnumeration<string>, you will receive two strings as arguments and must return a string as your aggregate result.

The Code

The following example creates a data source of integers and calls each of the standard aggregation methods. The same data source is used to demonstrate a custom aggregation method that totals the individual elements (equivalent to the Sum<> method). Finally, a string array is used as a data source for a custom aggregation that concatenates the individual elements.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_12
    {
        static void Main(string[] args)
        {
            // Define a numeric data source.
            int[] ds1 = { 1, 23, 37, 49, 143 };

            // Use the standard aggregation methods.
            Console.WriteLine("Standard aggregation methods");
            Console.WriteLine("Average: {0}", ds1.Average());
            Console.WriteLine("Count: {0}", ds1.Count());
            Console.WriteLine("Max: {0}", ds1.Max());
            Console.WriteLine("Min: {0}", ds1.Min());
            Console.WriteLine("Sum: {0}", ds1.Sum());

            // Perform our own sum aggregation.
            Console.WriteLine("
Custom aggregation");
            Console.WriteLine(ds1.Aggregate((total, elem) => total += elem));

            // Define a string data source.
            string[] ds2 = { "apple", "pear", "cherry" };

            // Perform a concat aggregation.
            Console.WriteLine("
String concatenation aggregation");
            Console.WriteLine(ds2.Aggregate((len, elem) => len += elem));
// Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

The program gives the following results:

Standard aggregation methods

Average: 50.6

Count: 5

Max: 143

Min: 1

Sum: 253



Custom aggregation

253



String concatenation aggregation

applepearcherry



Main method complete. Press Enter

Share Values Within a Query

Problem

You need to perform an operation on an element or a data source only once in a query.

Solution

Use the let keywords.

How It Works

If you need to perform the same operation in different parts of your query, you can store the result of an expression and use it several times. The example for this recipe demonstrates using the Sum aggregate method and using the result in both the where and select clauses of the query. Without the use of the let keyword, we would have to perform the aggregation in each clause. You can use let multiple times in a query.

The Code

The following example demonstrates how to use the let keyword to obtain the sum of the elements in a data source consisting of integers and use the result in the where and select sections of the same LINQ query.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_13
    {
        static void Main(string[] args)
        {
            // Define a numeric data source.
            int[] ds1 = { 1, 23, 37, 49, 143 };

            // Perform a query that shares a calculated value.
            IEnumerable<double> result1 = from e in ds1
                                            let avg = ds1.Average()
                                            where (e < avg)
                                            select (e + avg);

            Console.WriteLine("Query using shared value");
            foreach (double element in result1)
            {
                Console.WriteLine("Result element {0}", element);
            }
// Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Running the program gives the following results:

Query using shared value

Result element 51.6

Result element 73.6

Result element 87.6

Result element 99.6



Main method complete. Press Enter

Create Custom LINQ Extension Methods

Problem

You need to create a custom extension method that you can apply to LINQ data sources.

Solution

Create an extension method that works on instances of IEnumerable<>.

How It Works

Recipe 13-15 demonstrates how to create and use an extension method. The process for a LINQ extension method is the same, except that you specify the type to operate on as IEnumerable<>. All LINQ data sources implement IEnumerable<> or have a member that returns IEnumerable<>, so once you have defined your extension method, you will be able to apply it to any data source that contains elements of the type you have specified.

The Code

The following example demonstrates creating a customer LINQ extension method that removes the first and last element from string data sources:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    static class LINQExtensions
    {
        public static IEnumerable<string> RemoveFirstAndLast(
            this IEnumerable<string> source)
        {
            return source.Skip(1).Take(source.Count() - 2);
        }
    }

    class Recipe16_14
    {
        static void Main(string[] args)
        {
            // Create the data sources.
            string[] ds1 = {"apple", "banana", "pear", "fig"};
            IList<string> ds2 = new List<string>
                { "apple", "banana", "pear", "fig" };

            Console.WriteLine("Extension method used on string[]");
            IEnumerable<string> result1 = ds1.RemoveFirstAndLast();
            foreach (string element in result1)
            {
                Console.WriteLine("Result: {0}", element);
            }

            Console.WriteLine("
Extension method used on IList<string>");
            IEnumerable<string> result2 = ds1.RemoveFirstAndLast();
            foreach (string element in result2)
            {
                Console.WriteLine("Result: {0}", element);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Running the sample program gives the same result for the differing data sources:

Extension method used on string[]

Result: banana

Result: pear



Extension method used on IList<string>

Result: banana

Result: pear



Main method complete. Press Enter

Convert from IEnumerable<>

Problem

You want to use the results of a LINQ query in a form other than an enumeration.

Solution

Use one of the LINQ convenience extension methods to convert your result.

How It Works

It is not always convenient to have the results of a query as an IEnumerable. LINQ provides a series of extension methods that you can use to convert a query result into different types.

The Code

The following example creates a data source containing instances of the type Fruit, performs a LINQ query to select those with a short shelf life, and then converts the result to an array, a Dictionary, a List, and a Lookup, printing out the contents of each:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter16
{
    class Recipe16_15
    {
        static void Main(string[] args)
        {
            // Create the data sources.
            IEnumerable<Fruit> datasource = createData();

            // Perform a query.
            IEnumerable<Fruit> result = from e in datasource
                                              where e.ShelfLife <= 7
                                              select e;

            // Enumerate the result elements.
            Console.WriteLine("Results from enumeration");
            foreach (Fruit fruit in result)
            {
                Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                    fruit.Name, fruit.Color, fruit.ShelfLife);
            }

            // Convert the IEnumerable to an array.
            Fruit[] array = result.ToArray<Fruit>();
            // print out the contents of the array
            Console.WriteLine("
Results from array");
            for (int i = 0; i < array.Length; i++)
            {
                Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                                    array[i].Name,
                                    array[i].Color, array[i].ShelfLife);
            }

            // Convert the IEnumerable to a dictionary indexed by name.
            Dictionary<string, Fruit> dictionary = result.ToDictionary(e => e.Name);
            // print out the contents of the dictionary
            Console.WriteLine("
Results from dictionary");
            foreach (KeyValuePair<string, Fruit> kvp in dictionary)
            {
                Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                    kvp.Key, kvp.Value.Color, kvp.Value.ShelfLife);
            }
// Convert the IEnumerable to a list.
            IList<Fruit> list = result.ToList<Fruit>();
            // print out the contents of the list
            Console.WriteLine("
Results from list");
            foreach (Fruit fruit in list)
            {
                Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                    fruit.Name, fruit.Color, fruit.ShelfLife);
            }

            // Convert the IEnumerable to a lookup, indexed by color.
            ILookup<string, Fruit> lookup = result.ToLookup(e => e.Color);
            // Print out the contents of the list.
            Console.WriteLine("
Results from lookup");
            IEnumerator<IGrouping<string, Fruit>> groups = lookup.GetEnumerator();
            while (groups.MoveNext())
            {
                IGrouping<string, Fruit> group = groups.Current;
                Console.WriteLine("Group for {0}", group.Key);
                foreach (Fruit fruit in group)
                {
                    Console.WriteLine("Name: {0} Color: {1} Shelf Life: {2} days.",
                        fruit.Name, fruit.Color, fruit.ShelfLife);
                }
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter");
            Console.ReadLine();
        }

        static IList<Fruit> createData()
        {
            return new List<Fruit>()
            {
                new Fruit("apple", "red", 7),
                new Fruit("orange", "orange", 10),
                new Fruit("grape", "green", 4),
                new Fruit("fig", "brown", 12),
                new Fruit("plum", "red", 2),
                new Fruit("banana", "yellow", 10),
                new Fruit("cherry", "red", 7)
            };
        }
    }
    class Fruit
    {
public Fruit(string namearg, string colorarg, int lifearg)
        {
            Name = namearg;
            Color = colorarg;
            ShelfLife = lifearg;
        }
        public string Name { get; set; }
        public string Color { get; set; }
        public int ShelfLife { get; set; }
    }
}

Running the program gives the following results:

Results from enumeration

Name: apple Color: red Shelf Life: 7 days.

Name: grape Color: green Shelf Life: 4 days.

Name: plum Color: red Shelf Life: 2 days.

Name: cherry Color: red Shelf Life: 7 days.


Results from array

Name: apple Color: red Shelf Life: 7 days.

Name: grape Color: green Shelf Life: 4 days.

Name: plum Color: red Shelf Life: 2 days.

Name: cherry Color: red Shelf Life: 7 days.


Results from dictionary

Name: apple Color: red Shelf Life: 7 days.

Name: grape Color: green Shelf Life: 4 days.

Name: plum Color: red Shelf Life: 2 days.

Name: cherry Color: red Shelf Life: 7 days.
Results from list

Name: apple Color: red Shelf Life: 7 days.

Name: grape Color: green Shelf Life: 4 days.

Name: plum Color: red Shelf Life: 2 days.

Name: cherry Color: red Shelf Life: 7 days.


Results from lookup

Group for red

Name: apple Color: red Shelf Life: 7 days.

Name: plum Color: red Shelf Life: 2 days.

Name: cherry Color: red Shelf Life: 7 days.

Group for green

Name: grape Color: green Shelf Life: 4 days.


Main method complete. Press Enter
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.103.74