Page 1 of 1

LINQ by Example 1: Enumerable Methods A-L

#1 andrewsw  Icon User is offline

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3491
  • View blog
  • Posts: 11,910
  • Joined: 12-December 12

Posted 25 August 2014 - 11:15 AM

This tutorial provides an introduction to LINQ (Language-Integrated Query) and examples for nearly all of the LINQ Enumerable methods.

Where examples are omitted you should refer to the Microsoft documentation, Enumerable Methods, although Microsoft themselves do not provide examples for every method, particularly for the more obscure, or rarely used, methods or overloads.




Aggregate, All, Any, AsEnumerable, Average, Cast, Concat, Contains, Count, DefaultIfEmpty, Distinct, ElementAt, ElementAtOrDefault, Empty, Except, First, FirstOrDefault, GroupBy, GroupJoin, Intersect, Join, Last, LastOrDefault, LongCount, Max, Min, OfType, OrderBy, OrderByDescending, Range, Repeat, Reverse, Select, SelectMany, SequenceEqual, Single, SingleOrDefault, Skip, SkipWhile, Sum, Take, TakeWhile, ThenBy, ThenByDescending, ToArray, ToDictionary, ToList, ToLookup, Union, Where, Zip




Even though this tutorial is presented in a dictionary format I recommended that you read it, at least initially, sequentially. This will give you a feel for LINQ and what it can achieve, and you will begin to recognise patterns in the methods.

Other tutorials in this sequence:

LINQ by Example 2: Enumerable Methods M-Z
LINQ by Example 3: Methods Using IEqualityComparer
LINQ by Example 4: Query Expression Syntax
LINQ by Example 5: LINQ to XML Querying

The MS documentation and examples for the methods are generally good but:

  • They are spread over around 100 pages
  • There is a lot of repetition and you have to scroll quite far down to find the examples
  • The examples introduce new collections each time.

Creating new collections each time means that the pages are independent, but it does mean that you first have to read through and decipher the collections, before you can begin to understand the method itself.

My approach is to provide examples over just a few pages and to use, in the main, the same core-data, and a code-template that you can just copy the examples into. The main advantage is that you will become familiar with the core-data and so can concentrate on understanding the methods themselves.

The MS documentation is also littered with type-parameters like this:
SequenceEqual<TSource>(IEnumerable<TSource>, IEnumerable<TSource>, IEqualityComparer<TSource>)


This information is important but not immediately necessary to understand the methods - the type information can often be deduced from the examples. (This information initially gets in the way, in my opinion.)

Similarly, I am using arrays of anonymous types for the core-data. This largely removes all this type information. It does mean, however, that I have occasionally had to use var as the type; after all, with an anonymous-type, what would you put for T in Enumerable<T>?

What is LINQ?

LINQ (Language-Integrated Query) :MSDN

MSDN said:

Language-Integrated Query (LINQ) is a set of features introduced in Visual Studio 2008 that extends powerful query capabilities to the language syntax of C# and Visual Basic. LINQ introduces standard, easily-learned patterns for querying and updating data, and the technology can be extended to support potentially any kind of data store. Visual Studio includes LINQ provider assemblies that enable the use of LINQ with .NET Framework collections, SQL Server databases, ADO.NET Datasets, and XML documents.


Basically, LINQ allows us to perform queries against sequences of data. A query may result in a new collection, or a single (scalar) value: e.g. Min, Count. (It is also possible using LINQ to modify the collection itself.)

LINQ can query any collection implementing IEnumerable<>: arrays, any collection, XML DOM, or remote data sources. A string also implements IEnumerable<>, it is a collection of characters:

How to: Query for Characters in a String (LINQ) :MSDN

although LINQ is not usually necessary with a string: there are many string methods, or Regex, available.

There are a few versions of LINQ:

LINQ to Objects
LINQ to Entities
LINQ to SQL
LINQ to XML

We will be using LINQ to Objects, querying local objects/collections.

LINQ to Entities is for writing queries against the Entity Framework conceptual model.

LINQ to SQL is for querying relational data as objects. That is, instead of using unfamiliar SQL statements we can use C# LINQ syntax. SQL statements are strings and don't provide any intellisense (drop-down help), LINQ does.

LINQ to SQL is discouraged (by Microsoft) in favour of LINQ to Objects or LINQ to Entities. That is, instead of operating against data, a DAL (data access layer) should be created and queried. A DAL is an object model that represents the data-source. The Entity Framework provides this DAL as an ORM (object-relational map).

LINQ to XML

MSDN said:

LINQ to XML provides an in-memory XML programming interface that leverages the .NET Language-Integrated Query (LINQ) Framework.


Query Expression Syntax

Query Expression Syntax for Standard Query Operators :MSDN

Query expression syntax is more familiar for people who are comfortable with SQL statements:
    var people = from member in staff
                 where member.salary > 20000
                 select member;

    foreach (var person in people)
        Console.WriteLine(person.name + " " + person.salary.ToString("C0"));


These expressions are converted into calls to the Enumerable methods that we will be exploring.

Note that LINQ uses lazy evaluation; that is, 'people' in the above is not evaluated until enumerated in the foreach statement.

The expression syntax does not include syntax for all of the Enumerable methods, but you can generally use either. This SO topic highlights some differences.

It is also possible to mix expression and method (fluent) syntax:
    double peopleAvg = (from member in staff
                   where member.salary > 20000
                   select member).Average(x => x.salary);

    Console.WriteLine(peopleAvg.ToString("C0"));





The nomenclature for LINQ is a little confusing. Enumerable methods are referred to as query operators but, to my mind, from..where.. are operators. Methods are methods!

The methods are also referred to as fluent syntax, a term which I believe originates from the authors of the book C# in a Nutshell. Again, I think the query expressions are more suited to this 'fluent' description. Methods are methods!

from..where.. is also sometimes referred to as comprehension syntax, but not often.



The Template

This is the Console template, and data, to use for all of the examples:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace LINQTest {
    class Program {
        static void Main(string[] args) {
            int[] nos = { 34, 22, 22, 21, 40, 31, 32, 34, 40, 50, 10, 17 };
            string[] rainbow = { "Rod", "Jane", "Freddy", "Bungle", "Zippy", "George" };
            // Rainbow was a UK children's television series
            string[] extras = { "George", "Zippy", "Fred" };

            // arrays of anonymously typed elements
            var staff = new[] {
                new { name = "Bob Bones", salary = 20000, deptid = 1, grade = 6 },
                new { name = "Mary Muggins", salary = 22000, deptid = 2, grade = 6 },
                new { name = "Liz Elbow", salary = 22500, deptid = 2, grade = 5 },
                new { name = "Dave Diddly", salary = 28000, deptid = 3, grade = 4 },
                new { name = "Mary Pickles", salary = 27000, deptid = 1, grade = 4 },
                new { name = "Robert Piccalilli", salary = 18000, deptid = 1, grade = 6 }
            };

            var depts = new[] {
                new { deptid = 1, dept = "Marketing" },
                new { deptid = 2, dept = "Sales" },
                new { deptid = 3, dept = "Accounts" },
                new { deptid = 4, dept = "Human Resources" }
            };

            /****************************
             * TYPE YOUR EXAMPLE(S) HERE
             * **************************/

            Console.ReadKey();
        }
    }
}


Just copy examples to this template and run it. Some examples include additional, small or empty, collections, but you can still copy these examples into the template and run them.

The Methods

The method definitions are taken directly from the official documentation, other quotes from the docs I have placed within italics. In fact, most of the text is from the MS documentation, I have only occasionally considered it useful to add additional notes. (The examples, and comments within them are, of course, mine.)

One thing in particular that you need to understand with the examples is the use of lambda expressions. In x => 2 * x x is a placeholder for the current value and '2 * x' projects this value to a result.

In staff.Sum(x => x.salary) 'staff' is the collection, x represents (in turn) each member of this collection and 'x.salary' is the result (for each member) that the method uses.

Note: There are some methods for which I haven't provided an example: AsEnumerable, Cast, Empty, ToLookup. This is because I cannot add anything brief and useful beyond the Microsoft documentation.

Enumerable Methods :MSDN

Methods can be chained together, for example:
    IEnumerable<int> top3sal = staff.OrderByDescending(member => member.salary).Select(x => x.salary).Take(3);


although this is much easier to read like this:
    IEnumerable<int> top3sal = staff
        .OrderByDescending(member => member.salary)
        .Select(x => x.salary)
        .Take(3);





Aggregate

Applies an accumulator function over a sequence.

To simplify common aggregation operations, the standard query operators also include a general purpose count method, Count, and four numeric aggregation methods, namely Min, Max, Sum, and Average.

Quite often when you find yourself using Aggregate it just means that you haven't quite worked out how to write it in a simpler fashion, without Aggregate. Use Min, Max, etc., in preference where possible.

There are three different versions of Aggregate demonstrated, and numbered, here. (I have numbered them according to the order they appear in the MSDN documentation.) If this is your first read-through of this tutorial you might skip Aggregate and move on to All.

Aggregate 1: Applies an accumulator function over a sequence.
    string bowrain = rainbow.Aggregate((accum, next) => 
        accum + " " + new string(next.ToCharArray().Reverse().ToArray()));
    Console.WriteLine(bowrain);
    // Rod enaJ ydderF elgnuB yppiZ egroeG

    string ainbow = rainbow.Aggregate((accum, next) =>
        next.Substring(1) + " " + accum);
    Console.WriteLine(ainbow);
    // eorge ippy ungle reddy ane Rod


Aggregate 2: Applies an accumulator function over a sequence. The specified seed value is used as the initial accumulator value.
    // Count even numbers:
    int evens = nos.Aggregate(0, (total, next) =>
        next % 2 == 0 ? total + 1 : total);
    Console.WriteLine("Evens {0}", evens);      // 9

    int evensCount = nos.Count(x => x % 2 == 0);
    Console.WriteLine("Evens by Count, {0}", evensCount);      // 9


Aggregate 3: Applies an accumulator function over a sequence. The specified seed value is used as the initial accumulator value, and the specified function is used to select the result value.
    // Find names longer than "Geoff", return the longest in uppercase
    string longGeoff = rainbow.Aggregate("Geoff", (longest, next) =>
        next.Length > longest.Length ? next : longest,
        // return the final result as uppercase
        member => member.ToUpper());
    Console.WriteLine(longGeoff);       // FREDDY (George isn't > Freddy)


All

Determines whether all elements of a sequence satisfy a condition.
    bool allGT20k = staff.All(member => member.salary > 20000);
    Console.WriteLine("All salaries are{0}greater than 20k.", allGT20k ? " " : " not ");


Any

Determines whether any element of a sequence exists or satisfies a condition.

Enumerable.Any Method

Any 1: Determines whether a sequence contains any elements.
    Console.WriteLine("There are{0}staff.", staff.Any() ? " " : " no ");


Collections already have methods or properties to determine if they contain any elements, so this method isn't particularly useful. It is more useful when used in the where clause of a query expression:
    var owners = new[] { new { name = "Derek", pets = new[] { new { Name = "Tiddles" } } } };
    // which owners have Any pets..? There can't be any anyway!
    IEnumerable<string> petOwners = from owner in owners
                                    where owner.pets.Any()
                                    select owner.name;
    foreach (string ownerName in petOwners)
        Console.WriteLine(ownerName);


Any 2: Determines whether any element of a sequence satisfies a condition.
    // any grade 6 earning > 20k?
    bool grade620k = staff.Any(member => member.grade == 6 && member.salary > 20000);
    Console.WriteLine(grade620k ? "Some grade 6, > 20k" : "No grade 6, > 20k");


AsEnumerable

Returns the input typed as IEnumerable<T>.

The AsEnumerable<TSource>(IEnumerable<TSource>) method has no effect other than to change the compile-time type of source from a type that implements IEnumerable<T> to IEnumerable<T> itself.

Enumerable.AsEnumerable<TSource> Method :MSDN

Average

Computes the average of a sequence of (type) values.

There are 20 overloads of this method but, essentially, there are two distinct versions. There are overloads for each of the data-types Decimal, Double, Int32, Int64 and Single, and nullable versions of each of these. int? x declares a nullable integer, that can be assigned 'null':
int x? = null;


Average 1: Computes the average of a sequence of (type) values.
    Console.WriteLine("Average number is {0}", nos.Average());


If you point at the word Average in that code you will see that it returns System.Int32 by default.

Average 2: Computes the average of a sequence of (type) values that are obtained by invoking a transform function on each element of the input sequence. (This also has nullable versions for each type.)
    Console.WriteLine("Average salary is {0}", staff.Average(member => member.salary));


Cast

Casts the elements of an IEnumerable to the specified type.

An example of its use is for an ArrayList, which does not implement IEnumerable<T>. By calling Cast<TResult>(IEnumerable) on the ArrayList object, the standard query operators can then be used to query the sequence.

The equivalent in a query expression is from int i in objects.

Enumerable.Cast<TResult> Method :MSDN

Concat

Concatenates two sequences.

This is similar to Union but returns all elements, Union only returns unique elements.
    IEnumerable<string> namesAndDepts = staff.Select(member => member.name)
        .Concat(depts.Select(dep => dep.dept));
    // IEnumerable<string> requires 'using System.Collections.Generic;'

    foreach (string item in namesAndDepts) {
        Console.WriteLine(item);
    }
    //Dave Diddly
    //Mary Pickles
    //Robert Piccalilli
    //Marketing
    //Sales
    //Accounts
    //Human Resources (would return any duplicates as well)


Contains

Determines whether a sequence contains a specified element.

Contains 1: Determines whether a sequence contains a specified element by using the default equality comparer.
    bool hasBob = rainbow.Contains("Bob");
    Console.WriteLine("Rainbow has Bob? {0}", hasBob ? "Yes" : "No");


Contains 2: Determines whether a sequence contains a specified element by using a specified IEqualityComparer<T>. See Part 3.

Enumerable.Contains<TSource> Method (IEnumerable<TSource>, TSource, IEqualityComparer<TSource>) :MSDN

Count

Returns the number of elements in a sequence.

Count 1: Returns the number of elements in a sequence.
    int totalStaff = staff.Count();
    Console.WriteLine("There are {0} staff.", totalStaff);


Count 2: Returns a number that represents how many elements in the specified sequence satisfy a condition.
    int grade6s = staff.Count(member => member.grade == 6);
    Console.WriteLine("There are {0} grade 6's.", grade6s);

    int grade6sGT18 = staff.Count(member => member.grade == 6 && member.salary > 18000);
    Console.WriteLine("There are {0} grade 6's earning more than 18k.", grade6sGT18);


DefaultIfEmpty

Returns the elements of an IEnumerable<T>, or a default valued singleton collection if the sequence is empty.

These methods can be used to produce a left outer join when combined with the GroupJoin method.

DefaultIfEmpty 1: Returns the elements of the specified sequence or the type parameter's default value in a singleton collection if the sequence is empty. (The default value for reference and nullable types is null.)
    List<int> nothingToSeeHere = new List<int>();
    foreach (int number in nothingToSeeHere.DefaultIfEmpty())
        Console.WriteLine(number);      // 0, the default for int


DefaultIfEmpty 2: Returns the elements of the specified sequence or the specified value in a singleton collection if the sequence is empty.
    var patsy = new { name = "P Pats", salary = 0, deptid = 1, grade = 6 };

    foreach (var member in staff.DefaultIfEmpty(patsy)) {
        Console.WriteLine(member.name);
        // will print all names because staff is not empty
    }


Distinct

Returns distinct elements from a sequence.

Distinct 1: Returns distinct elements from a sequence by using the default equality comparer to compare values.
    foreach (int item in nos.Distinct()) {
        Console.WriteLine(item);
    }       // 34, 22, 21, 40, 31, 32, 50, 10, 17


Distinct 2: Returns distinct elements from a sequence by using a specified IEqualityComparer<T> to compare values. See Part 3.

ElementAt

Returns the element at a specified index in a sequence.
    Console.WriteLine("Second staff member is {0}", staff.ElementAt(1).name);
    // Mary Muggins


ElementAtOrDefault

Returns the element at a specified index in a sequence or a default value if the index is out of range.
    string jeff = rainbow.ElementAtOrDefault(10);
    Console.WriteLine(String.IsNullOrEmpty(jeff) ? "Jeffrey" : jeff);


Empty
Returns an empty IEnumerable(Of T) that has the specified type argument.

In some cases, this method is useful for passing an empty sequence to a user-defined method that takes an IEnumerable(Of T). It can also be used to generate a neutral element for methods such as Union.

Enumerable.Empty<TResult> Method :MSDN

Except

Produces the set difference of two sequences. The set difference is the members of the first sequence that don't appear in the second sequence.

Except 1: Produces the set difference of two sequences by using the default equality comparer to compare values.
    foreach (string intersect in rainbow.Except(extras)) {
        Console.WriteLine(intersect);
    }
    // Rod, Jane, Freddy, Bungle


Except 2: Produces the set difference of two sequences by using the specified IEqualityComparer<T> to compare values. See Part 3.

First

First 1: Returns the first element of a sequence.
    Console.WriteLine("First in Rainbow is {0}", rainbow.First()); // Rod


First 2: Returns the first element in a sequence that satisfies a specified condition.
    int firstGT35 = nos.First(x => x > 35);
    Console.WriteLine("First number > 35 is {0}", firstGT35.ToString()); // 40


FirstOrDefault

FirstOrDefault 1: Returns the first element of a sequence, or a default value if the sequence contains no elements.
    Console.WriteLine("First number is {0}", nos.FirstOrDefault()); // 34
    int[] nos2 = { };
    Console.WriteLine("First number is {0}", nos2.FirstOrDefault()); // 0


FirstOrDefault 2: Returns the first element of the sequence that satisfies a condition or a default value if no such element is found.
    int firstGT80 = nos.FirstOrDefault(x => x > 80);
    Console.WriteLine("First number > 80 is {0}", firstGT80); // 80


GroupBy

Groups the elements of a sequence.

There are eight overloads of this methods. I have followed the Microsoft documentation in only providing examples for two of them.

Enumerable.GroupBy Method :MSDN

GroupBy 3: Groups the elements of a sequence according to a specified key selector function and projects the elements for each group by using a specified function.
    IEnumerable<IGrouping<int, string>> byDept = staff.GroupBy(x => x.deptid, x => x.name);

    foreach (IGrouping<int, string> pers in byDept) {
        Console.WriteLine("Dept: {0}", depts.FirstOrDefault(x => x.deptid == pers.Key).dept);
        // pers.Key is the deptid - use this to get the dept-name
        foreach (string per in pers) {
            Console.WriteLine("\t{0}", per);
        }
    }


GroupBy 4: Groups the elements of a sequence according to a specified key selector function and creates a result value from each group and its key.
    var by5Grand = staff.GroupBy(
        member => Math.Floor((decimal)member.salary / 5000) * 5000,
        (agroup, members) => new {
            Key = agroup,
            Count = members.Count(),
            MinGrade = members.Min(member => member.grade)
        }).OrderBy(x => x.Key);             // OrderBy not required, but useful

    foreach (var group in by5Grand) {
        Console.WriteLine("\nFrom salary: " + group.Key.ToString("c"));
        Console.WriteLine("Number of staff: " + group.Count);
        Console.WriteLine("Minimum grade: " + group.MinGrade);
    }
    //From salary: £15,000.00
    //Number of staff: 1
    //Minimum grade: 6

    //From salary: £20,000.00
    //Number of staff: 3
    //Minimum grade: 5

    //From salary: £25,000.00
    //Number of staff: 2
    //Minimum grade: 4


GroupJoin

Correlates the elements of two sequences based on key equality, and groups the results.

GroupJoin has no direct equivalent in traditional relational database terms. However, this method does implement a superset of inner joins and left outer joins.

GroupJoin is interesting in that, as mentioned, it has no direct equivalent in SQL. The separation of the departments in the following code is usually achieved by iterating resultsets in code, using an if-statement to display, and indent, on separate lines.

GroupJoin 1: Correlates the elements of two sequences based on equality of keys and groups the results. The default equality comparer is used to compare keys.
    var groupDepts = depts.GroupJoin(staff,
        dep => dep.deptid,
        member => member.deptid,
        (dep, memberCollection) =>
            new {
                Department = dep.dept, 
                Members = memberCollection.Select(member => member.name)
            });

    foreach (var member in groupDepts) {
        Console.WriteLine(member.Department);
        foreach (string name in member.Members) {
            Console.WriteLine("\t{0}", name);
        }
    }
    //Marketing
    //    Bob Bones
    //    Mary Pickles
    //    Robert Piccalilli
    //Sales
    //    Mary Muggins
    //    Liz Elbow
    //Accounts
    //    Dave Diddly
    //Human Resources


GroupJoin 2: Correlates the elements of two sequences based on key equality and groups the results. A specified IEqualityComparer<T> is used to compare keys. See Part 3.

Intersect (see also Union)

Produces the set intersection of two sequences.

Intersect 1: Produces the set intersection of two sequences by using the default equality comparer to compare values.
    IEnumerable<string> inCommon = rainbow.Intersect(extras);
    foreach (string item in inCommon) {
        Console.WriteLine(item);        // Zippy and George
    }


Intersect 2: Produces the set intersection of two sequences by using the specified IEqualityComparer<T> to compare values. See Part 3.

Join

Correlates the elements of two sequences based on matching keys.

In relational database terms, the Join method implements an inner equijoin. 'Inner' means that only elements that have a match in the other sequence are included in the results. An 'equijoin' is a join in which the keys are compared for equality. A left outer join operation has no dedicated standard query operator, but can be performed by using the GroupJoin method.

Join 1: Correlates the elements of two sequences based on matching keys. The default equality comparer is used to compare keys.
    var joining = depts.Join(staff,
        dep => dep.deptid,
        member => member.deptid,
        (dep, member) => new { Department = dep.dept, Name = member.name });

    foreach (var item in joining) {
        Console.WriteLine("{0} - {1}", item.Department, item.Name);
    }
    //Marketing - Bob Bones
    //Marketing - Mary Pickles
    //Marketing - Robert Piccalilli
    //Sales - Mary Muggins
    //Sales - Liz Elbow
    //Accounts - Dave Diddly


Join 2: Correlates the elements of two sequences based on matching keys. A specified IEqualityComparer<T> is used to compare keys. See Part 3.

Last

Last 1: Returns the last element of a sequence.
    Console.WriteLine("Last number: {0}", nos.Last()); // 17


Last 2: Returns the last element of a sequence that satisfies a specified condition.
    var lastSalary = staff.Last(x => x.salary < 25000);
    Console.WriteLine("Last person earning < 25000 is {0}", lastSalary.name);
    // Robert Piccalilli


LastOrDefault

LastOrDefault 1: Returns the last element of a sequence, or a default value if the sequence contains no elements.
    string [] nowt = {};
    Console.WriteLine("The last item is {0}", nowt.LastOrDefault()); // ""


LastOrDefault 2: Returns the last element of a sequence that satisfies a condition or a default value if no such element is found.
    string lastRain = rainbow.LastOrDefault(x => x.Length == 5);
    Console.WriteLine("Last rainbow-name of length 5: {0}", lastRain); // Zippy


LongCount

LongCount 1: Returns an Int64 that represents the total number of elements in a sequence. Use this method rather than Count when you expect the result to be greater than MaxValue.
    Console.WriteLine("There are {0} staff", staff.LongCount());


LongCount 2: Returns an Int64 that represents how many elements in a sequence satisfy a condition.
    Console.WriteLine("{0} staff earn > 20k", staff.LongCount(x => x.salary > 20000));


Enumerable Methods M-Z
Methods Using IEqualityComparer
Query Expression Syntax
LINQ to XML Querying

This post has been edited by andrewsw: 04 September 2014 - 04:11 PM


Is This A Good Question/Topic? 1
  • +

Replies To: LINQ by Example 1: Enumerable Methods A-L

#2 andrewsw  Icon User is offline

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3491
  • View blog
  • Posts: 11,910
  • Joined: 12-December 12

Posted 25 August 2014 - 03:18 PM

I welcome comments or suggestions about this tutorial, particularly if there is a note or small example (using my data) that could be added to improve it. I reserve the right of refusal though ;)

For the curious or nostalgic:

Spoiler

This post has been edited by andrewsw: 25 August 2014 - 05:31 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1