Chapter 9 - Linq to Objects

The content and code of this article is referenced from book Pro C#5.0 and the .NET 4.5 Framework by Apress. The intention of the writing is to review the konwledge and gain better understanding of the .net framework. 

1. Understanding the role of LINQ

Prior to .net 3.5, interacting with a particular flavor of data required programmers to make use of very divese APIs. 

The LINQ API is an attempt to provide a consistent, symmetrical manner in which programmers can obtain and manipulate data in the broad sense of the term. In order to work with LINQ, you must make sure that every c# code file that contains LINQ queries imports the System.Linq. 

1.1 Applying LINQ querires to Primitives array

 1         static void QueryOverStrings()
 2         {
 3             string[] sports = {"football", "basketball", "cricket", "rugby", "tennis"};
 4 
 5             //Linq query to find out all sports contains text ball
 6             IEnumerable<string> subset = from b in sports
 7                                          where b.Contains ("ball")
 8                                          orderby b
 9                                          select b;
10             
11             foreach (string s in subset) {
12                 Console.WriteLine (s);
13             }
14 
15         }

We'll look at those keywords such as from, where, orderby later in this chapter, but it is pretty clear that we are querying string that contains word ball. Noticed that the returned sequence is held in a variable named subset, typed as a type that implement IEnumerable<T>. 

1.2 Linq and implicitly typed local variable

In some cases, the returning type from linq statement is not that obvious, and therefore, we need to use implicit variable and let .net runtime decide which type it should be. 

 1         static void QueryOverInts()
 2         {
 3             int[] numbers = { 10, 20, 30, 5 };
 4 
 5             var subset = from i in numbers
 6                          where i < 10
 7                          select i;
 8 
 9             foreach (var i in subset) {
10                 Console.WriteLine (i);
11             }
12         }

As a result, you will always use the implicit variable when capturing the resule of a LINQ query. Just remember, however, the real return value is a type implementing the generic IEnumerable<T> interface. 

 

1.3 Linq and extension method

Although the current example does not have you author any extension methods directly, you are in fact using them in the backgorund. Linq query expressions can be used to iterate over data containers that implement the generic IEnumerable<T> inteface. 

While System.Array does not directly implement the IEnumerable<T>, it gains the required functionality of this type via the static System.Linq.Enumerable class type. 

 

1.4 The role of deferred execution 

Another important point regarding LINQ query expressions is that they are not actually evaluated until you iterate over the sequence. The benefit of this approach is that you are able to apply the same Linq query multiple times to the same container. 

 1       static void Main(string[] args)
 2         {
 3             var myInt = new[] {10, 30, 40, 60};
 4 
 5             //Linq statement
 6             var subset = from i in myInt
 7                 where i > 10
 8                 select i;
 9 
10             //executed here
11             foreach (var i in subset)
12             {
13                 Console.WriteLine(i);
14             }
15         }

 

1.5 the role of immediate execution

When you need to evaluate a Linq expression from outside the confins of foreach logic, you are able to call any number of extension methods defined by Enumerable type as ToArray<T>(), ToList<T>(). 

1         static void Main(string[] args)
2         {
3             var myInt = new[] {10, 30, 40, 60};
4 
5             //immediate execution
6             var subset = (from i in myInt
7                 where i > 10
8                 select i).ToArray<int>();
9         }

Notice that the entire LINQ expression is wrapped within parentheses to cast it into the correct underlying type. 

 

2. Returning result of a linq query

Most often that not, LINQ queries are defined within the scope of a method or property. Moreover, the variable used to hold the result set will be stored in an implicitly typed local variable using the var keyword. And remember that implicit type variables cannot be used to define parameters, return values or fields of a class.

However, if you know the result set is a strongly typed data, you could use propert IEnumerable<T> to return it. 

        private static IEnumerable<String> GetString()
        {
            string[] names = {"jason", "smith", "jonh", "marine", "tim"};

            IEnumerable<String> subset = from n in names
                select n;
            return subset;
        }

 

2.1 returning linq results via immediate execution

 Because it is a bit inconvenient to operate on IEnumerable<T>, you could make use of immediate execution. 

        private static string[] GetString()
        {
            string[] names = {"jason", "smith", "jonh", "marine", "tim"};

            var subset = from n in names
                select n;
            return subset.ToArray();
        }

 

3. Applying linq query to collection objects

Linq query expression can also manipulate data within members of the System.Collections.Generic namespace, such as the List<T> type. 

 1         private static void GetCars(List<Car> myCars)
 2         {
 3             var cars = from c in myCars
 4                 where c.Speed > 50
 5                 select c;
 6 
 7             foreach (var car in cars)
 8             {
 9                 Console.WriteLine(car.Name);
10             }
11         }

 

3.1 Linq query to nongeneric collections

we all know that Linq is designed to work with any type implementing IEnumerable<T>. Thanksfully, it is still possible to iterate over data contained within nongeneric collection using the generic Enumerable.OfType<T>() extension method. 

 1        private static void GetCars()
 2         {
 3             var myCars = new ArrayList()
 4             {
 5                 new Car{Make = "make1", Name = "name1", Speed = 80},
 6                 new Car{Make = "make2", Name = "name2", Speed = 90},
 7                 new Car{Make = "make3", Name = "name3", Speed = 180},
 8                 new Car{Make = "make4", Name = "name4", Speed = 100},
 9             };
10 
11             //transfer ArrayList into Ienumerable<T> type
12             var myCarsEnum = myCars.OfType<Car>();
13 
14             var subset = from c in myCarsEnum
15                 where c.Speed > 80
16                 select c;
17 
18             foreach (var car in subset)
19             {
20                 Console.WriteLine(car.Name);
21             }
22         }

 

3.2 filtering data using ofType<T>

Nongeneric types are capable of containing any combination of items. And we can use oftype<T> to filter data to one specific type. 

 1         private static void OfTypesAsFilter()
 2         {
 3             var myStuff = new ArrayList();
 4             myStuff.AddRange(new Object[]{10, 400, 0, false, new Car(), "string"});
 5 
 6             var myInts = myStuff.OfType<int>();
 7             foreach (int i in myInts)
 8             {
 9                 Console.WriteLine(i);
10             }
11         }

 

4. Investigating C# LINQ query operators

 C# defines a good number of operators out of the box. 

Operator   Meaning
from, in Used to define the backbone for any LINQ expression
where       Used to define a restriction to filter data
select Used to select a sequence data from container
join, on, equals, into Performs joins based on specific key. 
orderby, ascending, descending sort the data in certain order
group by yields a subset with data grouped by a specific value

In addition to those operator, System.Linq.Enumerable class provides a set of extension methods, such as Reverse<>(), ToArray<>(), ToList<>(), Distinct<>(), Union<>(), Interset<>() etc. 

 

4.1 Basic selection syntax

var result = from matchingItem in container select matchingItem;

The item after from operator represents an item that matches the LINQ query criteria, which can be named anything you choose. The item after in operator represents the data container to search, can be array, xml or collection etc)

            //select all products
            var allProducts = from p in itemsInStock
                select p;

            //select all product name
            var allProductName = from p in itemsInStock
                select p.Name;

 

4.2 Obtaining subset of data 

var result = from item in container where expression select item;

Notice that where operator expects an expression that resolves to a boolean. 

            //select product with stock > 10
            var subset = from p in itemsInStock
                where p.NumberInStock > 10
                select p;

 

4.3 Projecting new data type

It is possible to project new forms of data from an existing data source. 

            //return anonymous type data
            var subset = from p in itemsInStock
                where p.NumberInStock > 10
                select new {p.Name, p.Description};

Always remember that when you have a LINQ query that make use of a projection, you don't know the underlying data type. In these cases, the var keyword is mandatory. 

When you need to return projected data to the caller, one approach is to transform the query result into System.Array using ToArray(). 

1         private static Array GetSubset(ProductInfo[] products)
2         {
3             var subset = from product in products
4                 where product.NumberInStock > 20
5                 select new {product.Name, product.NumberInStock};
6 
7             return subset.ToArray();
8         }

The obvious problem is that you lose any strong typing, as each item in array object is assumed to be of type object. 

 

4.4 Obtaining counts using enumerable

        private static void GetCount(IEnumerable<ProductInfo> products )
        {
            int count = (from product in products
                where product.NumberInStock > 50
                select product).Count();

        }

 

4.5 Reverse result sets

1         private static void ReverseResult(IEnumerable<ProductInfo> products)
2         {
3             var allProducts = from p in products select p;
4             foreach (var prod in allProducts.Reverse())
5             {
6                 Console.WriteLine(prod.ToString());                
7             }
8         }

 

4.6 Sort expression

1         private static void Sort(IEnumerable<ProductInfo> products)
2         {
3             var result = from product in products
4                 where product.NumberInStock > 50
5                 orderby product.Name
6                 select product;
7         }

 

4.7 Combine results

The Enumerable class supports a set of extension methods that allows you to use two or more Linq queries to find union, differences, and intersections of data. 

Except() returns a linq result set that contains the differences between containers. 

        private static void ExceptResult()
        {
            var myList = new List<string>(){"BMW", "Benz", "VW"};
            var mySecondList = new List<string>(){"BMW", "LandRover", "Benz"};

            var carDiff = (from c in myList select c).Except(from c2 in mySecondList select c2);

            foreach (var s in carDiff)
            {
                Console.WriteLine(s);  //print out VW, which dose not exist in second list
            }
        }

Intersect() returns data exist in both linq statement. In this case, it will return "BMW", "Benz".

Union() returns a result set includes all members of linq querys, without duplicate. 

Concat() returns a result set that is a direct concatenation of linq results. 

 

4.8 Removing duplicates

        private static void ExceptResult()
        {
            var myList = new List<string>(){"BMW", "Benz", "VW"};
            var mySecondList = new List<string>(){"BMW", "LandRover", "Benz"};

            var carDiff = (from c in myList select c).Concat(from c2 in mySecondList select c2);

            foreach (var s in carDiff.Distinct())  //distinct value
            {
                Console.WriteLine(s); 
            }
        }

 

4.9 Linq aggregation operation

1         private static void Sort(IEnumerable<ProductInfo> products)
2         {
3             var result = from p in products select p.NumberInStock;
4             int max = result.Max();
5             int min = result.Min();
6         }

 

5. The internal representation of LINQ query statements

In face, C# compiler translates all C# linq operators into calls on methods of Enumerable class. 

A great many of the methods of Enumerable have been prototyped to take delegates as arguments. In particular, many methods require a generic delegate named Func<>.

 

5.1 Building query expressions using enumerable type and lambda expression

Keep in mind that the linq query operators used here are simply shorthand versions for calling various extension methods defined by the Enumerable type. 

 1         static void QueryWithEnumerable()
 2         {
 3             string[] games = {"Dota", "LOL", "Fallout", "Uncharted" };
 4 
 5             var subset = games.Where (game => game.Contains ("a")).OrderBy (game => game).Select (game => game);
 6 
 7             foreach (var g in subset) {
 8                 Console.WriteLine (g);
 9             }
10         }

Here, we are calling the Where() extension method on the string array. The Enumerable.Where() method requires a System.Func<T1, TResult> delegate parameter. The first parameter of this delegate represents the IEnumerable<T> compatible data to process, while the second type parameter represents the method result data. 

        static void QueryWithEnumerable()
        {
            string[] games = {"Dota", "LOL", "Fallout", "Uncharted" };

            var gamesWithG = games.Where (game => game.Contains ("a"));
            var orderGame = gamesWithG.OrderBy (game => game);
            var subset = orderGame.Select (game => game);

            foreach (var g in subset) {
                Console.WriteLine (g);
            }
        }

You can, actually, break the statement into discrete chunks. 

As you might agree, building a linq query expression using the methods of Enumerable class directly is much more verbose than making use of C# operator. And you will typically need to quthor lambda expressions to allow the input data to be processed by the underlying delegate target. 

 

5.2 Building query expression using the enumerable type and anonymous type

static void QueryWithEnumerable()
        {
            string[] games = {"Dota", "LOL", "Fallout", "Uncharted" };

            //builing delegate using anonymous methods
            Func<string, bool> searchFilter = delegate(string name) {
                return games.Contains ("a");
            };

            Func<string, string> itemToProcess = delegate(string s) {
                return s;
            };
                
            var subset = games.Where (searchFilter).OrderBy (itemToProcess).Select (itemToProcess);

            foreach (var g in subset) {
                Console.WriteLine (g);
            }
        }

Given that lambda expressions are simply shorthand notations anonymous method, we can also implment the query using anonymous methods. 

 

5.3 Summary

Query expressions are created using various C# query operator.

Query operators are simple shorthand notations for invoking extension methods defined by the System.Linq.Enumerable

Many methods of Enumerable requires delegate (Func<>) as parameters

Any method requiring a delegate parameter can instead be passed a lambda expression

Lambda expressions are simply anonymous methods in disguise

Anonymous methods are shorthand notations for allocating a raw delegate and manually building a delegate target method. 

 

posted @ 2015-04-11 10:01  tim_bo  阅读(151)  评论(0编辑  收藏  举报