O'Reilly logo

C# 6.0 Cookbook, 4th Edition by Jay Hilyard, Stephen Teilhet

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Language Integrated Query (LINQ) and Lambda Expressions

4.0 Introduction

Language Integrated Query (LINQ) is a great way to access data from many different sources. LINQ provides a single querying model that can operate against different data domains individually or all together in a single query. LINQ brings the ability to query data to .NET languages, and some of the languages have provided extensions to make its use even more intuitive. One of these languages is C#; there are a number of extensions to the language in C# that help to facilitate querying in a rich and intuitive manner.

Traditional object-oriented programming is based on an imperative style wherein developers describe in detail not only what they want to happen, but also exactly how it should be performed through code. LINQ helps to take coding down a more declarative path that facilitates describing what the developer wants to do instead of detailing how to accomplish the goal. LINQ also enables a more functional style of programming. These changes can dramatically shorten the amount of code it takes to perform some tasks. That said, object-oriented programming is still very much alive and well in C# and .NET, but for the first time the language is offering you the chance to choose the style of programming based on your needs. Note, however, that LINQ will not fit into every scenario and is not a replacement for good design or practice. You can write bad code using LINQ just as you can write bad object-oriented or procedural code. The trick, as it always has been, is to figure out when it is appropriate to use which technique.

The initial version of LINQ encompasses a number of data domains:

  • LINQ to Objects

  • LINQ to XML

  • LINQ to ADO.NET

  • LINQ to SQL

  • LINQ to DataSet

  • LINQ to Entities

As you begin your examination of LINQ, it is easy to think of it as a new object relational mapping layer, or some neat new widget on IEnumerable<T>, or a new XML API, or even just an excuse to not write SQL directly anymore. You can use it as any of these things, but we would encourage you to instead think of LINQ as how your program asks for, calculates, or transforms sets of data from both single and disparate sources. It takes a bit of time and playing with LINQ for its functionality to click, but once it does, you will be surprised at what you can do with it. This chapter begins to show some of what is possible with LINQ and will hopefully get you thinking of which of your scenarios are applicable to this new capability in C#.

To write the LINQ query expressions to specify criteria and select data, we use lambda expressions. They are a convenient way to represent the delegate passed to LINQ queries like System.Func<T, TResult> when the Enumerable.Where method is called as part of narrowing down a result set. Lambda expressions are functions with a different syntax that enables them to be used in an expression context instead of the usual object-oriented method of being a member of a class. This means that with a single syntax, we can express a method definition, declaration, and the invocation of delegate to execute it, just as anonymous methods can, but with a more terse syntax. A projection is a lambda expression that translates one type into another.

A lambda expression looks like this:

j => j * 42

This means “using j as the parameter to the function, j goes to the result of j * 42.” The => can be read as “goes to” for both this and a projection declared like so:

j => new { Number = j*42 };

If you think about it, in C# 1.0 you could do the same thing:

public delegate int IncreaseByANumber(int j);
public delegate int MultipleIncreaseByANumber(int j, int k, int l);

static public int MultiplyByANumber(int j)
{
    return j * 42;
}


public static void ExecuteCSharp1_0()
{
    IncreaseByANumber increase =
       new IncreaseByANumber(
           DelegatesEventsLambdaExpressions.MultiplyByANumber);
    Console.WriteLine(increase(10));
}

In C# 2.0 with anonymous methods, the C# 1.0 syntax could be reduced to the following example, as it is no longer necessary to provide the name for the delegate since all we want is the result of the operation:

public delegate int IncreaseByANumber(int j);

public static void ExecuteCSharp2_0()
{
    IncreaseByANumber increase =
       new IncreaseByANumber(
        delegate(int j)
        {
            return j * 42;
        });
    Console.WriteLine(increase(10));
}

This brings us back to C# today and lambda expressions, where we can now just write:

public static void ExecuteCSharp6_0()
{
    // declare the lambda expression
    IncreaseByANumber increase = j => j * 42;
    // invoke the method and print 420 to the console
    Console.WriteLine(increase(10));

    MultipleIncreaseByANumber multiple = (j, k, l) => ((j * 42) / k) % l;
    Console.WriteLine(multiple(10, 11, 12));
}

Type inference helps the compiler to infer the type of j from the declaration of the IncreaseByANumber delegate type. If there were multiple arguments, then the lambda expression could look like this:

MultipleIncreaseByANumber multiple = (j, k, l) => ((j * 42) / k) % l;
Console.WriteLine(multiple(10, 11, 12));

This chapter’s recipes make use of delegates, events, and lambda expressions. Among other topics, these recipes cover:

  • Handling each method invoked in a multicast delegate separately

  • Synchronous delegate invocation versus asynchronous delegate invocation

  • Enhancing an existing class with events

  • Various uses of lambda expressions, closures, and functors

If you are not familiar with delegates, events, or lambda expressions, you should read the MSDN documentation on these topics. There are also good tutorials and example code showing you how to set them up and use them in a basic fashion.

4.1 Querying a Message Queue

Problem

You want to be able to query for messages with specific criteria from an existing message queue.

Solution

Use the EnumerableMessageQueue class to write a LINQ query to retrieve messages using the System.Messaging.MessageQueue type:

string queuePath = @".\private$\LINQMQ";
EnumerableMessageQueue messageQueue = null;
if (!EnumerableMessageQueue.Exists(queuePath))
    messageQueue = EnumerableMessageQueue.Create(queuePath);
else
    messageQueue = new EnumerableMessageQueue(queuePath);

using (messageQueue)
{
    BinaryMessageFormatter messageFormatter = new BinaryMessageFormatter();

    // Query the message queue for specific messages with the following criteria:
    // 1) the label must be less than 5
    // 2) the name of the type in the message body must contain 'CSharpRecipes.D'
    // 3) the results should be in descending order by type name (from the body)

    var query = from Message msg in messageQueue
        // The first assignment to msg.Formatter is so that we can touch the
        // Message object. It assigns the BinaryMessageFormatter to each message
        // instance so that it can be read to determine if it matches the 
        // criteria. This is done and then checks that the formatter was 
        // correctly assigned by performing an equality check which satisfies the  
        // where clause's need for a Boolean result while still executing the  
        // assignment of the formatter.
    where ((msg.Formatter = messageFormatter) == messageFormatter) &&
                    int.Parse(msg.Label) < 5 &&
                    msg.Body.ToString().Contains("CSharpRecipes.D")
                orderby msg.Body.ToString() descending
                select msg;

    // check our results for messages with a label > 5 and containing
    // a 'D' in the name
    foreach (var msg in query)
        Console.WriteLine($"Label: {msg.Label}" +
            $" Body: {msg.Body}");
}

The query retrieves the data from the MessageQueue by selecting the messages where the Label is a number greater than 5 and the message body contains the text “CSharpRecipes.D”. These messages are then returned, sorted by the message body in descending order.

Discussion

There are a number of keywords in this LINQ code that were not previously used to access a message queue:

var
var instructs the compiler to infer the variable type from the right side of the statement. In essence, the variable type is determined by what is on the right side of the operator, separating the var keyword and the expression. This allows for implicitly typed local variables.
from
The from keyword sets out the source collection to query against and a range variable to represent a single element from that collection. It is always the first clause in a query operation. This may seem counterintuitive if you are used to SQL and expect select to be first, but if you consider that we need to know what to work on before we determine what to return, it makes sense. In fact, if we weren’t already used to how SQL works, it would be SQL that seems counterintuitive.
where
The where keyword specifies the constraints by which the elements to return are filtered. Each condition must evaluate to a Boolean result, and when all expressions evaluate to true, the element of the collection is allowed to be selected.
orderby
orderby indicates that the result set should be sorted according to the criteria specified. The default order is ascending, and elements use the default comparer.
select
select allows the projection of an entire element from the collection, the construction of a new type with parts of that element and other calculated values, or a subcollection of items into the result.

The messageQueue collection is of type System.Messaging.MessageQueue, which implements the IEnumerable interface. This is important, as the LINQ methods provided need a set or collection to implement at least IEnumerable in order to work with that set or collection. It is possible to implement a set of extension methods that do not need IEnumerable, but most people will not have the need to do so. It is even better when the set or collection implements IEnumerable<T>, as LINQ then knows the type of element in the set or collection with which it is working.

Even though MessageQueue implements the IEnumerable interface (but not IEnumerable<T>), the original implementation of IEnumerable had some problems, so now if you try to use it, it doesn’t actually enumerate any results. You will also get a deprecation warning reading This method returns a MessageEnumerator that implements RemoveCurrent family of methods incorrectly. Please use GetMessageEnumerator2 instead. if you try to use GetEnumerator on MessageQueue.

To address this, we have created the EnumerableMessageQueue, which derives from MessageQueue but uses the suggested GetMessageEnumerator2 method to implement both IEnumerable and IEnumerable<Message>. So we can just use the EnumerableMessageQueue instance with LINQ:

public class EnumerableMessageQueue : MessageQueue, IEnumerable<Message>
{
    public EnumerableMessageQueue() :
        base() { }
    public EnumerableMessageQueue(string path) : base(path) { }
    public EnumerableMessageQueue(string path, bool sharedModeDenyReceive) :
        base (path, sharedModeDenyReceive) { }
    public EnumerableMessageQueue(string path, QueueAccessMode accessMode) :
        base (path, accessMode) { }
    public EnumerableMessageQueue(string path, bool sharedModeDenyReceive,
        bool enableCache) : base (path, sharedModeDenyReceive, enableCache) { }
    public EnumerableMessageQueue(string path, bool sharedModeDenyReceive,
        bool enableCache, QueueAccessMode accessMode) :
            base (path, sharedModeDenyReceive, enableCache, accessMode) { }

    public static new EnumerableMessageQueue Create(string path) =>
        Create(path, false);

    public static new EnumerableMessageQueue Create(string path,
        bool transactional)
    {
        // Use MessageQueue directly to make sure the queue exists
        if (!MessageQueue.Exists(path))
            MessageQueue.Create(path, transactional);
        // create the enumerable queue once we know it is there
        return new EnumerableMessageQueue(path);
    }

    public new MessageEnumerator GetMessageEnumerator()
    {
        throw new NotSupportedException("Please use GetEnumerator");
    }

    public new MessageEnumerator GetMessageEnumerator2()
    {
        throw new NotSupportedException("Please use GetEnumerator");
    }

    IEnumerator<Message> IEnumerable<Message>.GetEnumerator()
    {
        //NOTE: In .NET 3.5, you used to be able to call "GetEnumerator" on
        //MessageQueue via normal LINQ semantics and have it work. Now we 
        //have to call GetMessageEnumerator2, as GetEnumerator has been 
        //deprecated. Now we use EnumerableMessageQueue which deals with 
        //this for us...
        MessageEnumerator messageEnumerator = base.GetMessageEnumerator2();
        while (messageEnumerator.MoveNext())
        {
            yield return messageEnumerator.Current;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        //NOTE: In .NET 3.5, you used to be able to call "GetEnumerator" on
        //MessageQueue via normal LINQ semantics and have it work. Now we have
        // to call GetMessageEnumerator2, as GetEnumerator has been deprecated.
        //Now we use EnumerableMessageQueue which deals with this for us...
        MessageEnumerator messageEnumerator = base.GetMessageEnumerator2();
        while (messageEnumerator.MoveNext())
        {
            yield return messageEnumerator.Current;
        }
    }
}

Now the query provides the element type Message, as shown in the from line in the LINQ query:

var query = from Message msg in messageQueue

In the Solution, the messages in the queue have been sent with BinaryFormatter. To be able to query against them correctly, the Formatter property must be set on each Message before it is examined as part of the where clause:

// The first assignment to msg.Formatter is so that we can touch the
// Message object. It assigns the BinaryMessageFormatter to each message
// instance so that it can be read to determine if it matches the criteria.
// This is done, and then it checks that the formatter was correctly assigned
// by performing an equality check, which satisfies the where clause's need
// for a boolean result, while still executing the assignment of the formatter.
where ((msg.Formatter = messageFormatter) == messageFormatter) &&

There are two uses of the var keyword in the Solution code:

var query = from Message msg in messageQueue
            ...

foreach (var msg in query)
...

The first usage infers that an IEnumerable<Message> will be returned and assigned to the query variable. The second usage infers that the type of msg is Message because the query variable is of type IEnumerable<Message> and the msg variable is an element from that IEnumerable.

It is also worth noting that when performing operations in a query, you can use actual C# code to determine the conditions, and there is more than just the predetermined set of operators. In the where clause of this query, both int.Parse and string. Contains are used to help filter messages:

int.Parse(msg.Label) > 5 &&
msg.Body.ToString().Contains('CSharpRecipes.D')

Finally, the orderby is used to sort the results in descending order:

orderby msg.Body.ToString() descending

See Also

Recipe 4.9, and the “MessageQueue class,” “Implicitly typed local variable,” “from keyword,” “where keyword,” “orderby keyword,” and “select keyword” topics in the MSDN documentation.

4.2 Using Set Semantics with Data

Problem

You would like to work with your collections using set operations for union, intersections, exceptions, and distinct items.

Solution

Use the set operators provided as part of the standard query operators to perform those operations.

Distinct:

IEnumerable<string> whoLoggedIn =
    dailySecurityLog.Where(
        logEntry => logEntry.Contains("logged in")).Distinct();

Union:

// Union
Console.WriteLine("Employees for all projects");
var allProjectEmployees = project1.Union(project2.Union(project3));

Intersect:

// Intersect
Console.WriteLine("Employees on every project");
var everyProjectEmployees = project1.Intersect(project2.Intersect(project3));

Except:

Console.WriteLine("Employees on only one project");
var onlyProjectEmployees = allProjectEmployees.Except(unionIntersect);

Discussion

The standard query operators are the set of methods that represent the LINQ pattern. This set includes operators to perform many different types of operations, such as filtering, projection, sorting, grouping, and many others, including set operations.

The set operations for the standard query operators are:

  • Distinct

  • Union

  • Intersect

  • Except

The Distinct operator extracts all nonduplicate items from the collection or result set being worked with. Say, for example, that we had a set of strings representing today’s login and logout behavior for a virtual machine in a common use development environment:

string[] dailySecurityLog = {
        "Rakshit logged in",
        "Aaron logged in",
        "Rakshit logged out",
        "Ken logged in",
        "Rakshit logged in",
        "Mahesh logged in",
        "Jesse logged in",
        "Jason logged in",
        "Josh logged in",
        "Melissa logged in",
        "Rakshit logged out",
        "Mary-Ellen logged out",
        "Mahesh logged in",
        "Alex logged in",
        "Scott logged in",
        "Aaron logged out",
        "Jesse logged out",
        "Scott logged out",
        "Dave logged in",
        "Ken logged out",
        "Alex logged out",
        "Rakshit logged in",
        "Dave logged out",
        "Josh logged out",
        "Jason logged out"};

From that collection, we would like to determine the list of people who logged in to the virtual machine today. Since people can log in and log out many times during the course of a day or remain logged in for the whole day, we need to eliminate the duplicate login entries. Distinct is an extension method on the System.Linq.Enumerable class (which implements the standard query operators) that we can call on the string array (which supports IEnumerable) in order to get the distinct set of items from the collection. (For more information on extension methods, see Recipe 4.4.) To get the set, we use another of the standard query operators, where, which takes a lambda expression that determines the filter criteria for the set and examines each string in the IEnumerable<string> to determine if the string has “logged in.” Lambda expressions are inline statements (similar to anonymous methods) that can be used in place of a delegate. (See Recipe 4.12 for more on lambda expressions.) If the strings have logged in, then they are selected. Distinct narrows down the set of strings further to eliminate duplicate “logged in” records, leaving only one per user:

IEnumerable<string> whoLoggedIn =
    dailySecurityLog.Where(
        logEntry => logEntry.Contains("logged in")).Distinct();
Console.WriteLine("Everyone who logged in today:");
foreach (string who in whoLoggedIn)
    Console.WriteLine(who);

To make things a bit more interesting, for the rest of the operators we will work with sets of employees on various projects in a company. An Employee is a pretty simple class with a Name and overrides for ToString, Equals, and GetHashCode, as shown here:

public class Employee
{
    public string Name { get; set; }
    public override string ToString() => this.Name;
    public override bool Equals(object obj) =>
        this.GetHashCode().Equals(obj.GetHashCode());
    public override int GetHashCode() => this.Name.GetHashCode();
}

You might wonder why Equals and GetHashCode are overloaded for such a simple class. The reason is that when LINQ compares elements in the sets or collections, it uses the default comparison, which in turn uses Equals and GetHashCode to determine if one instance of a reference type is the same as another. If you do not include the semantics in the reference type class to provide the same hash code or equals value when the data for two instances of the object is the same, then by default the instances will be different, as two reference types have different hash codes by default. We override that so that if the Name is the same for each Employee, the hash code and the equals value will both correctly identify the instances as the same. There are also overloads for the set operators that take a custom comparer, which would also allow you to make this determination even for classes for which you can’t make the changes to Equals and GetHashCode.

Having done this, we can now assign Employees to projects like so:

Employee[] project1 = {
            new Employee(){ Name = "Rakshit" },
            new Employee(){ Name = "Jason" },
            new Employee(){ Name = "Josh" },
            new Employee(){ Name = "Melissa" },
            new Employee(){ Name = "Aaron" },
            new Employee() { Name = "Dave" },
            new Employee() {Name = "Alex" } };
Employee[] project2 = {
            new Employee(){ Name = "Mahesh" },
            new Employee() {Name = "Ken" },
            new Employee() {Name = "Jesse" },
            new Employee(){ Name = "Melissa" },
            new Employee(){ Name = "Aaron" },
            new Employee(){ Name = "Alex" },
            new Employee(){ Name = "Mary-Ellen" } };
Employee[] project3 = {
            new Employee(){ Name = "Mike" },
            new Employee(){ Name = "Scott" },
            new Employee(){ Name = "Melissa" },
            new Employee(){ Name = "Aaron" },
            new Employee(){ Name = "Alex" },
            new Employee(){ Name = "Jon" } };

To find all Employees on all projects, we can use Union to get all nonduplicate Employees in all three projects and write them out, as Union will give you all distinct Employees of all three projects:

// Union
Console.WriteLine("Employees for all projects");
var allProjectEmployees = project1.Union(project2.Union(project3));
foreach (Employee employee in allProjectEmployees)
    Console.WriteLine(employee);

We can then use Intersect to get the Employees on every project, as Intersect will determine the common Employees from each project and return those:

// Intersect
Console.WriteLine("Employees on every project");
var everyProjectEmployees = project1.Intersect(project2.Intersect(project3));
foreach (Employee employee in everyProjectEmployees)
    Console.WriteLine(employee);

Finally, we can use a combination of Union and Except to find Employees that are on only one project, as Except filters out all Employees on more than one project:

// Except
var intersect1_3 = project1.Intersect(project3);
var intersect1_2 = project1.Intersect(project2);
var intersect2_3 = project2.Intersect(project3);
var unionIntersect = intersect1_2.Union(intersect1_3).Union(intersect2_3);

Console.WriteLine("Employees on only one project");
var onlyProjectEmployees = allProjectEmployees.Except(unionIntersect);
foreach (Employee employee in onlyProjectEmployees)
    Console.WriteLine(employee);

Output for the code shown is:

Everyone who logged in today:
Rakshit logged in
Aaron logged in
Ken logged in
Mahesh logged in
Jesse logged in
Jason logged in
Josh logged in
Melissa logged in
Alex logged in
Scott logged in
Dave logged in
Employees for all projects
Rakshit
Jason
Josh
Melissa
Aaron
Dave
Alex
Mahesh
Ken
Jesse
Mary-Ellen
Mike
Scott
Jon
Employees on every project
Melissa
Aaron
Alex
Employees on only one project
Rakshit
Jason
Josh
Dave
Mahesh
Ken
Jesse
Mary-Ellen
Mike
Scott
Jon

See Also

The “Standard Query Operators,” “Distinct method,” “Union method,” “Intersect method,” and “Except method” topics in the MSDN documentation.

4.3 Reusing Parameterized Queries with LINQ to SQL

Problem

You need to execute the same parameterized query multiple times with different parameter values, but you want to avoid the overhead of parsing the query expression tree to build the parameterized SQL each time the query executes.

Solution

Use the CompiledQuery.Compile method to build an expression tree that will not have to be parsed each time the query is executed with new parameters:

var GetEmployees =
    CompiledQuery.Compile((NorthwindLinq2Sql.NorthwindLinq2SqlDataContext nwdc,
        string ac, string ttl) =>
            from employee in nwdc.Employees
            where employee.HomePhone.Contains(ac) &&
                    employee.Title == ttl
            select employee);

var northwindDataContext = new NorthwindLinq2Sql.NorthwindLinq2SqlDataContext();

The first time the query executes is when it actually compiles (where GetEmployees is called the first time in the foreach loop). Every other iteration in this loop and in the next loop uses the compiled version, avoiding the expression tree parsing:

foreach (var employee in GetEmployees(northwindDataContext, "(206)",
    "Sales Representative"))
    Console.WriteLine($"{employee.FirstName} {employee.LastName}");

foreach (var employee in GetEmployees(northwindDataContext, "(71)", 
    "Sales Manager"))
    Console.WriteLine($"{employee.FirstName} {employee.LastName}");

Discussion

We used var for the query declaration because it was cleaner, but in this case var is actually:

System.Func<NorthwindLinq2Sql.NorthwindLinq2SqlDataContext, string, string,
System.Linq.IQueryable<NorthwindLinq2Sql.Employee>>

which is the delegate signature for the lambda expression we created that contains the query. That’s right—all this crazy query stuff, and we just instantiated a delegate. To be fair, the Func delegate was brought about in the System namespace as part of LINQ, so do not despair: we are still doing cool stuff!

This illustrates that we are not returning an IEnumerable- or IQueryable-based result set from Compile, but rather an expression tree that represents the potential for a query rather than the query itself. Once we have that tree, LINQ to SQL then has to convert it to actual SQL that can run against the database. Interestingly enough, if we had put in a call to string.Format as part of detecting the area code in the employee’s home phone number, we would get a NotSupportedException informing us that string.Format can’t be translated to SQL:

    where employee.HomePhone.Contains(string.Format($"({ac})")) &&

System.NotSupportedException:
Method 'System.String Format(System.String,System.Object)'
  has no supported translation to SQL.

This is understandable, as SQL has no concept of .NET Framework methods for performing actions, but keep in mind as you design your queries that this is a limitation of using LINQ to SQL.

After the first execution, the query is compiled, and for every iteration after that, we do not pay the transformation cost for turning the expression tree into the parameterized SQL.

Compiling your queries is recommended for parameterized queries that get a lot of traffic, but if a query is infrequently used, it may not be worth the effort. As always, profile your code to see the areas where doing so could be useful.

Note that in the templates for Entity Framework 5 and up, you could not use CompiledQuery with the context that is generated, because those templates were redone to use DbContext, not ObjectContext, and CompiledQuery.Compile requires an ObjectContext. The good news is that if you are using Entity Framework 5 and up, DbContext does precompilation of queries for you! You can still use CompiledQuery with the LINQ to SQL data context.

Microsoft recommends using a DbContext in new development, but if you have existing code on prior data access mechanisms, CompiledQuery can still help!

See Also

The “CompiledQuery.Compile method” and “Expression Trees” topics in the MSDN documentation.

4.4 Sorting Results in a Culture-Sensitive Manner

Problem

You want to ensure that when you sort in a query, the sort order is for an application-specific culture that may not be the same as the thread’s current culture.

Solution

Use the overload of the OrderBy query operator, which accepts a custom comparer, in order to specify the culture in which to perform comparisons:

// Create CultureInfo for Danish in Denmark.
CultureInfo danish = new CultureInfo("da-DK");
// Create CultureInfo for English in the U.S.
CultureInfo american = new CultureInfo("en-US");

CultureStringComparer comparer = 
   new CultureStringComparer(danish,CompareOptions.None);
var query = names.OrderBy(n => n, comparer);

Discussion

Handling localization issues such as sorting for a specific culture is a relatively trivial task in .NET if the current culture of the thread is the one you want to use. To access the framework classes that assist in handling culture issues in C, you include the System.Globalization namespace. You’d include this namespace in order to make the code in the Solution run. One example of overriding the thread’s current culture would be an application that needs to display a sorted list of Danish words on a version of Windows that is set for US English. This functionality might also be useful if you are working with a multitenant web service or website with global clients.

The current thread in the application may have a CultureInfo for “en-US” and, by default, the sort order for OrderBy will use the current culture’s sort settings. To specify that this list should sort according to Danish rules instead, you must do a bit of work in the form of a custom comparer:

CultureStringComparer comparer =
    new CultureStringComparer(danish,CompareOptions.None);

The comparer variable is an instance of the custom comparer class CultureStringComparer, which is defined to implement the IComparer<T> interface specialized for strings. This class is used to provide the culture settings for the sort order:

public class CultureStringComparer : IComparer<string>
{
    private CultureStringComparer()
    {
    }

    public CultureStringComparer(CultureInfo cultureInfo, CompareOptions options)
    {
        if (cultureInfo == null)
            throw new ArgumentNullException(nameof(cultureInfo));

        CurrentCultureInfo = cultureInfo;
        Options = options;
    }

    public int Compare(string x, string y) =>
        CurrentCultureInfo.CompareInfo.Compare(x, y, Options);

    public CultureInfo CurrentCultureInfo { get; set; }

    public CompareOptions Options { get; set; }
}

To demonstrate how this could be used, first we compile a list of words to order by. Since the Danish language treats the character Æ as an individual letter, sorting it after Z in the alphabet, and the English language treats the character Æ as a special symbol, sorting it before the letter A in the alphabet, this example will demonstrate the sort difference:

string[] names = { "Jello", "Apple", "Bar", "Æble",
    "Forsooth", "Orange", "Zanzibar" };

Now, we can set up the CultureInfos for both Danish and US English and call OrderBy with the comparer specific to each culture. This query does not use the query expression syntax, but rather uses the functional style of IEnumerable<string>.OrderBy():

// Create CultureInfo for Danish in Denmark.
CultureInfo danish = new CultureInfo("da-DK");
// Create CultureInfo for English in the U.S.
CultureInfo american = new CultureInfo("en-US");

CultureStringComparer comparer =
    new CultureStringComparer(danish,CompareOptions.None);
var query = names.OrderBy(n => n, comparer);
Console.WriteLine($"Ordered by specific culture : " +
    $"{comparer.CurrentCultureInfo.Name}");
foreach (string name in query)
    Console.WriteLine(name);

comparer.CurrentCultureInfo = american;
query = names.OrderBy(n => n, comparer);
Console.WriteLine($"Ordered by specific culture : " +
    $"{comparer.CurrentCultureInfo.Name}");
foreach (string name in query)
    Console.WriteLine(name);


query = from n in names
        orderby n
        select n;
Console.WriteLine("Ordered by Thread.CurrentThread.CurrentCulture : " +
    $"{ Thread.CurrentThread.CurrentCulture.Name}");
foreach (string name in query)
    Console.WriteLine(name);


// Create CultureInfo for Danish in Denmark.
    CultureInfo danish = new CultureInfo("da-DK");
    // Create CultureInfo for English in the U.S.
    CultureInfo american = new CultureInfo("en-US");

    CultureStringComparer comparer =
        new CultureStringComparer(danish, CompareOptions.None);
    var query = names.OrderBy(n => n, comparer);
    Console.WriteLine("Ordered by specific culture : " +
        comparer.CurrentCultureInfo.Name);
    foreach (string name in query)
    {
        Console.WriteLine(name);
    }
    comparer.CurrentCultureInfo = american;
    query = names.OrderBy(n => n, comparer);
    Console.WriteLine("Ordered by specific culture : " +
        comparer.CurrentCultureInfo.Name);
    foreach (string name in query)
    {
        Console.WriteLine(name);
    }

These output results show that the word Æble is last in the Danish list and first in the US English list:

Ordered by specific culture : da-DK
Apple
Bar
Forsooth
Jello
Orange
Zanzibar
Æble
Ordered by specific culture : en-US
Æble
Apple
Bar
Forsooth
Jello
Orange
Zanzibar

See Also

The “OrderBy,” “CultureInfo,” and “IComparer<T>” topics in the MSDN documentation.

4.5 Adding Functional Extensions for Use with LINQ

Problem

There are operations you perform on collections frequently that currently reside in utility classes. You would like to be able to have these operations be used on collections in a more seamless manner than having to pass the reference to the collection to the utility class.

Solution

Use extension methods to help achieve a more functional style of programming for your collection operations. For example, to add a weighted moving average calculation operation to numeric collections, implement a set of WeightedMovingAverage extension methods in a static class and then call them as part of those collections:

decimal[] prices = new decimal[10] { 13.5M, 17.8M, 92.3M, 0.1M, 15.7M,
                                     19.99M, 9.08M, 6.33M, 2.1M, 14.88M };
Console.WriteLine(prices.WeightedMovingAverage());

double[] dprices = new double[10] { 13.5, 17.8, 92.3, 0.1, 15.7,
                                    19.99, 9.08, 6.33, 2.1, 14.88 };
Console.WriteLine(dprices.WeightedMovingAverage());

float[] fprices = new float[10] { 13.5F, 17.8F, 92.3F, 0.1F, 15.7F,
                                  19.99F, 9.08F, 6.33F, 2.1F, 14.88F };
Console.WriteLine(fprices.WeightedMovingAverage());

int[] iprices = new int[10] { 13, 17, 92, 0, 15,
                              19, 9, 6, 2, 14 };
Console.WriteLine(iprices.WeightedMovingAverage());

long[] lprices = new long[10] { 13, 17, 92, 0, 15,
                                19, 9, 6, 2, 14 };
Console.WriteLine(lprices.WeightedMovingAverage());

To provide WeightedMovingAverage for the full range of numeric types, methods for both the nullable and non-nullable numeric types are included in the LinqExtensions class:

public static class LinqExtensions
{
    public static decimal? WeightedMovingAverage(
        this IEnumerable<decimal?> source)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));

        decimal aggregate = 0.0M;
        decimal weight;
        int item = 1;
        // count how many items are not null and use that
        // as the weighting factor
        int count = source.Count(val => val.HasValue);
        foreach (var nullable in source)
        {
            if (nullable.HasValue)
            {
                weight = item / count;
                aggregate += nullable.GetValueOrDefault() * weight;
                count++;
            }
        }
        if (count > 0)
            return new decimal?(aggregate / count);
        return null;
    }
    // The same method pattern as above is followed for each of the other
    // types and its nullable counterparts (double / double?, int / int?, etc.)
}

Discussion

Extension methods allow you to create operations that appear to be part of a collection. They are static methods that can be called as if they were instance methods, allowing you to extend existing types. Extension methods must also be declared in static classes that are not nested. Once a static class is defined with extension methods, the using directive for the namespace of the class makes those extensions available in the source file.

Note

If an instance method exists with the same signature as the extension method, the extension method will never be called. Conflicting extension method declarations will resolve to the method in the closest enclosing namespace.

You cannot use extension methods to create:

  • Properties (get and set methods)

  • Operators (+, -, = , etc.)

  • Events

To declare an extension method, you specify the this keyword in front of the first parameter of a method declaration, and the type of that parameter is the type being extended. For example, in the Nullable<decimal> version of the WeightedMovingAverage method, collections that support IEnumerable<decimal?> (or IEnumerable<Nullable<decimal>>) are supported:

public static decimal? WeightedMovingAverage(this IEnumerable<decimal?> source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    decimal aggregate = 0.0M;
    decimal weight;
    int item = 1;
    // count how many items are not null and use that
    // as the weighting factor
    int count = source.Count(val => val.HasValue);
    foreach (var nullable in source)
    {
        if (nullable.HasValue)
        {
            weight = item / count;
            aggregate += nullable.GetValueOrDefault() * weight;
            count++;
        }
    }
    if (count > 0)
        return new decimal?(aggregate / count);
    return null;
}

The extension methods that support much of the LINQ functionality are on the System.Linq.Extensions class, including an Average method. The Average method has most of the numeric types but does not provide an overload for short (Int16). We can easily rectify that by adding one ourselves for short and Nullable<short>:

public static double? Average(this IEnumerable<short?> source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    double aggregate = 0.0;
    int count = 0;
    foreach (var nullable in source)
    {
        if (nullable.HasValue)
        {
            aggregate += nullable.GetValueOrDefault();
            count++;
        }
    }
    if (count > 0)
        return new double?(aggregate / count);
    return null;
}

public static double Average(this IEnumerable<short> source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    double aggregate = 0.0;
    // use the count of the items from the source
    int count = source.Count();
    foreach (var value in source)
    {
        aggregate += value;
    }
    if (count > 0)
        return aggregate / count;
    else
        return 0.0;
}

public static double? Average<TSource>(this IEnumerable<TSource> source,
    Func<TSource, short?> selector) =>
        source.Select<TSource, short?>(selector).Average();

public static double Average<TSource>(this IEnumerable<TSource> source,
    Func<TSource, short> selector) =>
        source.Select<TSource, short>(selector).Average();

#endregion // Extend Average

We can then call Average on short-based collections just like WeightedMovingAverage:

short[] sprices = new short[10] { 13, 17, 92, 0, 15, 19, 9, 6, 2, 14 };
Console.WriteLine(sprices.WeightedMovingAverage());
// System.Linq.Extensions doesn't implement Average for short but we do for them!
Console.WriteLine(sprices.Average());

See Also

The “Extension Methods” topic in the MSDN documentation.

4.6 Querying and Joining Across Data Repositories

Problem

You have two sets of data from different data domains, and you want to be able to combine the data and work with it.

Solution

Use LINQ to bridge across the disparate data domains. LINQ is intended to be used in the same manner across different data domains and supports combining those sets of data with join syntax.

To demonstrate this, we will join an XML file full of categories with the data from a database (Northwind) of products to create a new set of data for product information that holds the product name, the category description, and the category name:

Northwind dataContext =
    new Northwind(Settings.Default.NorthwindConnectionString);
ProductsTableAdapter adapter = new ProductsTableAdapter();
Products products = new Products();
adapter.Fill(products._Products);

XElement xmlCategories = XElement.Load("Categories.xml");

var expr = from product in products._Products
           where product.Units_In_Stock > 100
           join xc in xmlCategories.Elements("Category")
           on product.Category_ID equals int.Parse(
               xc.Attribute("CategoryID").Value)
           select new
           {
               ProductName = product.Product_Name,
               Category = xc.Attribute("CategoryName").Value,
               CategoryDescription = xc.Attribute("Description").Value
           };

foreach (var productInfo in expr)
{
    Console.WriteLine("ProductName: " + productInfo.ProductName +
        " Category: " + productInfo.Category +
        " Category Description: " + productInfo.CategoryDescription);
}

The new set of data is printed to the console, but this could easily have been rerouted to another method, transformed in another query, or written out to a third data format:

ProductName: Grandma's Boysenberry Spread Category: Condiments Category
Description: Sweet and savory sauces, relishes, spreads, and seasonings
ProductName: Gustaf's Knäckebröd Category: Grains/Cereals Category Description:
Breads, crackers, pasta, and cereal
ProductName: Geitost Category: Dairy Products Category Description:
Cheeses
ProductName: Sasquatch Ale Category: Beverages Category Description: Soft drinks,
coffees, teas, beer, and ale
ProductName: Inlagd Sill Category: Seafood Category Description:
Seaweed and fish
ProductName: Boston Crab Meat Category: Seafood Category Description:
Seaweed and fish
ProductName: Pâté chinois Category: Meat/Poultry Category Description:
Prepared meats
ProductName: Sirop d'érable Category: Condiments Category Description:
Sweet and savory sauces, relishes, spreads, and seasonings
ProductName: Röd Kaviar Category: Seafood Category Description:
Seaweed and fish
ProductName: Rhönbräu Klosterbier Category: Beverages Category Description:
Soft drinks, coffees, teas, beer, and ale

Discussion

The Solution combines data from two different data domains: XML and a SQL database. Before LINQ, to do this you would have had to not only create a third data repository by hand to hold the result, but also write the specific code for each domain to query that domain for its part of the data (XPath for XML; SQL for database) and then manually transform the result sets from each domain into the new data repository. LINQ enables you to write the query to combine the two sets of data, automatically constructs a type via projecting a new anonymous type, and places the pertinent data in the new type, all in the same syntax. Not only does this simplify the code, but it also allows you to concentrate more on getting the data you want and less on determining exactly how to read both data domains.

This example uses both LINQ to DataSet and LINQ to XML to access the multiple data domains:

var dataContext = new NorthwindLinq2Sql.NorthwindLinq2SqlDataContext();

ProductsTableAdapter adapter = new ProductsTableAdapter();
Products products = new Products();
adapter.Fill(products._Products);

XElement xmlCategories = XElement.Load("Categories.xml");

NorthwindLinq2SqlDataContext is a DataContext class. A DataContext is analogous to an ADO.NET Connection and Command object rolled into one. You use it to establish your connection, execute queries, or access tables directly via entity classes. You can generate a DataContext directly from the database through Visual Studio by adding a new “LINQ to SQL Classes” item. This provides access to the local Northwind.mdf database for the query. A Products DataSet is loaded from the Products table in the Northwind.mdf database for use in the query.

XElement is one of the main classes in LINQ to XML. It enables the loading of existing XML, creation of new XML, or retrieval of the XML text for the element via ToString. Example 4-1 shows the Categories.xml file that will be loaded. For more on XElement and LINQ to XML, see Chapter 10.

Example 4-1. Categories.xml
<?xml version="1.0" encoding="utf-8"?>
<Categories>
  <Category Id="1" Name="Beverages"
    Description="Soft drinks, coffees, teas, beers, and ales" />
  <Category Id="2" Name="Condiments"
    Description="Sweet and savory sauces, relishes, spreads, and seasonings" />
  <Category Id="3" Name="Confections"
    Description="Desserts, candies, and sweet breads" />
  <Category Id="4" Name="Dairy Products" Description="Cheeses" />
  <Category Id="5" Name="Grains/Cereals"
    Description="Breads, crackers, pasta, and cereal" />
  <Category Id="6" Name="Meat/Poultry" Description="Prepared meats" />
  <Category Id="7" Name="Produce" Description="Dried fruit and bean curd" />
  <Category Id="8" Name="Seafood" Description="Seaweed and fish" />
</Categories>

The two sets of data are joined via LINQ and, in particular, the join keyword. We join the data by matching the category ID in the Products table with the category ID in the XML file to combine the data. In SQL terms, the join keyword represents an inner join:

var expr = from product in products._Products
            where product.UnitsInStock > 100
            join xc in xmlCategories.Elements("Category")
            on product.CategoryID equals int.Parse(xc.Attribute("Id").Value)

Once the join result is complete, we project a new type using the select keyword:

select new
{
    ProductName = product.ProductName,
    Category = xc.Attribute("Name").Value,
    CategoryDescription = xc.Attribute("Description").Value
};

This allows us to combine different data elements from the two sets of data to make a third set that can look completely different than either of the original two.

Doing joins on two sets of database data would be a bad idea, as the database can do this much faster for those sets, but when you need to join disparate data sets, LINQ can lend a helping hand.

See Also

The “join keyword,” “System.Data.Linq.DataContext,” and “XElement” topics in the MSDN documentation.

4.7 Querying Configuration Files with LINQ

Problem

Data sets can be stored in many different locations, such as configuration files. You want to be able to query your configuration files for sets of information.

Solution

Use LINQ to query against the configuration sections. In the following example, we do this by retrieving all chapter titles with even numbers and the word and in the title from the custom configuration section containing chapter information:

CSharpRecipesConfigurationSection recipeConfig =
    ConfigurationManager.GetSection("CSharpRecipesConfiguration") as
CSharpRecipesConfigurationSection;

var expr = from ChapterConfigurationElement chapter in
    recipeConfig.Chapters.OfType<ChapterConfigurationElement>()
            where (chapter.Title.Contains("and")) &&
             ((int.Parse(chapter.Number) % 2) == 0)
            select new
            {
                ChapterNumber = $"Chapter {chapter.Number}",
                chapter.Title
            };

foreach (var chapterInfo in expr)
    Console.WriteLine($"{chapterInfo.ChapterNumber} : {chapterInfo.Title}");

The configuration section being queried looks like this:

<CSharpRecipesConfiguration CurrentEdition="4">
  <Chapters>
    <add Number="1" Title="Classes and Generics" />
    <add Number="2" Title="Collections, Enumerators, and Iterators" />
    <add Number="3" Title="Data Types" />
    <add Number="4" Title="LINQ &amp; Lambda Expressions" />
    <add Number="5" Title="Debugging and Exception Handling" />
    <add Number="6" Title="Reflection and Dynamic Programming" />
    <add Number="7" Title="Regular Expressions" />
    <add Number="8" Title="Filesystem I/O" />
    <add Number="9" Title="Networking and Web" />
    <add Number="10" Title="XML" />
    <add Number="11" Title="Security" />
    <add Number="12" Title="Threading, Synchronization, and Concurrency" />
    <add Number="13" Title="Toolbox" />
  </Chapters>
  <Editions>
    <add Number="1" PublicationYear="2004" />
    <add Number="2" PublicationYear="2006" />
    <add Number="3" PublicationYear="2007" />
    <add Number="4" PublicationYear="2015" />
  </Editions>
</CSharpRecipesConfiguration>

The output from the query is:

Chapter 2 : Collections, Enumerators, and Iterators
Chapter 6 : Reflection and Dynamic Programming
Chapter 12 : Threading, Synchronization, and Concurrency

Discussion

Configuration files in .NET play a significant role in achieving manageability and ease of deployment for .NET-based applications. It can be challenging to get all of the various settings right in the hierarchy of configuration files that can affect an application, so understanding how to write utilities to programmatically check configuration file settings is of great use during development, testing, deployment, and ongoing management of an application.

Note

To access the configuration types, you will need to reference the System.Configuration assembly.

Even though the ConfigurationElementCollection class (the base of data sets in configuration files) supports only IEnumerable and not IEnumerable<T>, we can still use it to get the elements we need by using the OfType<ChapterConfigurationElement> method on the collection, which selects elements of that type from the collection:

var expr = from ChapterConfigurationElement chapter in
    recipeConfig.Chapters.OfType<ChapterConfigurationElement>()

ChapterConfigurationElement is a custom configuration section class that holds the chapter number and title:

/// <summary>
/// Holds the information about a chapter in the configuration file
/// </summary>
public class ChapterConfigurationElement : ConfigurationElement
{
    /// <summary>
    /// Default constructor
    /// </summary>
    public ChapterConfigurationElement()
    {
    }

    /// <summary>
    /// The number of the Chapter
    /// </summary>
    [ConfigurationProperty("Number", IsRequired=true)]
    public string Number
    {
        get { return (string)this["Number"]; }
        set { this["Number"] = value; }
    }

    /// <summary>
    /// The title of the Chapter
    /// </summary>
    [ConfigurationProperty("Title", IsRequired=true)]
    public string Title
    {
        get { return (string)this["Title"]; }
        set { this["Title"] = value; }
    }
}

This technique can be used on the standard configuration files, such as machine.config, as well. This example determines which sections in machine.config require access permissions. For this collection, OfType<ConfigurationSection> is used, as this is a standard section:

System.Configuration.Configuration machineConfig =
    ConfigurationManager.OpenMachineConfiguration();

var query = from ConfigurationSection section in
machineConfig.Sections.OfType<ConfigurationSection>()
            where section.SectionInformation.RequirePermission
            select section;

foreach (ConfigurationSection section in query)
    Console.WriteLine(section.SectionInformation.Name);

The sections detected will look something like this:

configProtectedData
satelliteassemblies
assemblyBinding
system.codedom
system.data.dataset
system.data.odbc
system.data
system.data.oracleclient
system.data.oledb
uri
system.windows.forms
system.runtime.remoting
runtime
system.diagnostics
windows
mscorlib
system.webServer
system.data.sqlclient
startup

See Also

The “Enumerable.OfType method,” “ConfigurationSectionCollection class” and “ConfigurationElementCollection class” topics in the MSDN documentation.

4.8 Creating XML Straight from a Database

Problem

You want to be able to take a data set from a database and represent it as XML.

Solution

Use LINQ to Entities and LINQ to XML to retrieve and transform the data all in one query. In this case, we will select the top five customers in the Northwind database whose contact is the owner and those owners who placed orders totaling more than $10,000, then create XML containing the company name, contact name, phone number, and total amount of the orders. Finally, the results are written out to the BigSpenders.xml file:

NorthwindEntities dataContext = new NorthwindEntities();

// Log the generated SQL to the console
dataContext.Database.Log = Console.WriteLine;

// select the top 5 customers whose contact is the owner and
// those owners placed orders spending more than $10000 this year
var bigSpenders = new XElement("BigSpenders",
    from top5 in
        (
            (from customer in
                    (
                        from c in dataContext.Customers
                            // get the customers where the contact is the 
                            // owner and they placed orders
                        where c.ContactTitle.Contains("Owner")
                        && c.Orders.Count > 0
                        join orderData in
                            (
                                from c in dataContext.Customers
                                // get the customers where the contact is the 
                                // owner and they placed orders
                                where c.ContactTitle.Contains("Owner")
                                && c.Orders.Count > 0
                                from o in c.Orders
                                    // get the order details
                                join od in dataContext.Order_Details
                                    on o.OrderID equals od.OrderID
                                select new
                                {
                                    c.CompanyName,
                                    c.CustomerID,
                                    o.OrderID,
                                    // have to calc order value from orderdetails
                                    //(UnitPrice*Quantity as Total)- 
                                    // (Total*Discount) as NetOrderTotal
                                    NetOrderTotal = (
                                        (((double)od.UnitPrice) * od.Quantity) -
                                        ((((double)od.UnitPrice) * od.Quantity) *
                                          od.Discount))
                                }
                            )
                        on c.CustomerID equals orderData.CustomerID
                        into customerOrders
                        select new
                        {
                            c.CompanyName,
                            c.ContactName,
                            c.Phone,
                            // Get the total amount spent by the customer
                            TotalSpend = customerOrders.Sum(order =>
                                            order.NetOrderTotal)
                        }
                    )
                    // only worry about customers that spent > 10000
                where customer.TotalSpend > 10000
                orderby customer.TotalSpend descending
                // only take the top 5 spenders
                select new
                {
                    CompanyName = customer.CompanyName,
                    ContactName = customer.ContactName,
                    Phone = customer.Phone,
                    TotalSpend = customer.TotalSpend
                }).Take(5)
        ).ToList()
    // format the data as XML
    select new XElement("Customer",
            new XAttribute("companyName", top5.CompanyName),
            new XAttribute("contactName", top5.ContactName),
            new XAttribute("phoneNumber", top5.Phone),
            new XAttribute("amountSpent", top5.TotalSpend)));
using (XmlWriter writer = XmlWriter.Create("BigSpenders.xml"))
{
    bigSpenders.WriteTo(writer);
}
Note

When building larger queries, you may find it is sometimes easier to use the functional approach (.Join()) to build up the query instead of the query expression manner (join x on y equals z) if you have done more C# than SQL.

Discussion

LINQ to SQL is the part of LINQ to ADO.NET that facilitates rapid database development. It is targeted at the scenarios where you want to program almost directly against the database schema. Most of these scenarios have one-to-one correlations between strongly typed classes and database tables. If you are in more of an enterprise development scenario with lots of stored procedures and databases that have moved away from “one table equals one entity” scenarios, you would want to look into LINQ to Entities.

You can access the LINQ to SQL visual designer by adding a new or opening an existing “LINQ to SQL Classes” item (*.dbml file) to the project, which opens the designer. This helps you to build out the DataContext and entity classes for your database, which can then be used with LINQ (or other programming constructs if you wish). A DataContext is analogous to an ADO.NET Connection and Command object rolled into one. You use it to establish your connection, execute queries, or access tables directly via entity classes. The NorthwindLinq2Sql data context is a strongly typed instance of a DataContext and is partially shown here:

public partial class NorthwindLinq2SqlDataContext : System.Data.Linq.DataContext
{

    private static System.Data.Linq.Mapping.MappingSource mappingSource = new
AttributeMappingSource();

#region Extensibility Method Definitions
partial void OnCreated();
partial void InsertCategory(Category instance);
partial void UpdateCategory(Category instance);
partial void DeleteCategory(Category instance);
partial void InsertTerritory(Territory instance);
partial void UpdateTerritory(Territory instance);
partial void DeleteTerritory(Territory instance);
partial void InsertCustomerCustomerDemo(CustomerCustomerDemo instance);
partial void UpdateCustomerCustomerDemo(CustomerCustomerDemo instance);
partial void DeleteCustomerCustomerDemo(CustomerCustomerDemo instance);
partial void InsertCustomerDemographic(CustomerDemographic instance);
partial void UpdateCustomerDemographic(CustomerDemographic instance);
partial void DeleteCustomerDemographic(CustomerDemographic instance);
partial void InsertCustomer(Customer instance);
partial void UpdateCustomer(Customer instance);
partial void DeleteCustomer(Customer instance);
partial void InsertEmployee(Employee instance);
partial void UpdateEmployee(Employee instance);
partial void DeleteEmployee(Employee instance);
partial void InsertEmployeeTerritory(EmployeeTerritory instance);
partial void UpdateEmployeeTerritory(EmployeeTerritory instance);
partial void DeleteEmployeeTerritory(EmployeeTerritory instance);
partial void InsertOrder_Detail(Order_Detail instance);
partial void UpdateOrder_Detail(Order_Detail instance);
partial void DeleteOrder_Detail(Order_Detail instance);
partial void InsertOrder(Order instance);
partial void UpdateOrder(Order instance);
partial void DeleteOrder(Order instance);
partial void InsertProduct(Product instance);
partial void UpdateProduct(Product instance);
partial void DeleteProduct(Product instance);
partial void InsertRegion(Region instance);
partial void UpdateRegion(Region instance);
partial void DeleteRegion(Region instance);
partial void InsertShipper(Shipper instance);
partial void UpdateShipper(Shipper instance);
partial void DeleteShipper(Shipper instance);
partial void InsertSupplier(Supplier instance);
partial void UpdateSupplier(Supplier instance);
partial void DeleteSupplier(Supplier instance);
#endregion

    public NorthwindLinq2SqlDataContext() :
     base(
 global::NorthwindLinq2Sql.Properties.Settings.Default.NorthwindConnectionString,
     mappingSource)    {
        OnCreated();
    }

    public NorthwindLinq2SqlDataContext(string connection) :
            base(connection, mappingSource)
    {
        OnCreated();
    }

    public NorthwindLinq2SqlDataContext(System.Data.IDbConnection connection) :
            base(connection, mappingSource)
    {
        OnCreated();
    }

    public NorthwindLinq2SqlDataContext(string connection,
        System.Data.Linq.Mapping.MappingSource mappingSource) :
            base(connection, mappingSource)
    {
        OnCreated();
    }

    public NorthwindLinq2SqlDataContext(System.Data.IDbConnection connection,
System.Data.Linq.Mapping.MappingSource mappingSource) :
            base(connection, mappingSource)
    {
        OnCreated();
    }

    public System.Data.Linq.Table<Category> Categories
    {
        get
        {
            return this.GetTable<Category>();
        }
    }

    public System.Data.Linq.Table<Territory> Territories
    {
        get
        {
            return this.GetTable<Territory>();
        }
    }

    public System.Data.Linq.Table<CustomerCustomerDemo> CustomerCustomerDemos
    {
        get
        {
            return this.GetTable<CustomerCustomerDemo>();
        }
    }

    public System.Data.Linq.Table<CustomerDemographic> CustomerDemographics
    {
        get
        {
            return this.GetTable<CustomerDemographic>();
        }
    }

    public System.Data.Linq.Table<Customer> Customers
    {
        get
        {
            return this.GetTable<Customer>();
        }
    }

    public System.Data.Linq.Table<Employee> Employees
    {
        get
        {
            return this.GetTable<Employee>();
        }
    }

    public System.Data.Linq.Table<EmployeeTerritory> EmployeeTerritories
    {
        get
        {
            return this.GetTable<EmployeeTerritory>();
        }
    }

    public System.Data.Linq.Table<Order_Detail> Order_Details
    {
        get
        {
            return this.GetTable<Order_Detail>();
        }
    }

    public System.Data.Linq.Table<Order> Orders
    {
        get
        {
            return this.GetTable<Order>();
        }
    }

    public System.Data.Linq.Table<Product> Products
    {
        get
        {
            return this.GetTable<Product>();
        }
    }

    public System.Data.Linq.Table<Region> Regions
    {
        get
        {
            return this.GetTable<Region>();
        }
    }

    public System.Data.Linq.Table<Shipper> Shippers
    {
        get
        {
            return this.GetTable<Shipper>();
        }
    }

    public System.Data.Linq.Table<Supplier> Suppliers
    {
        get
        {
            return this.GetTable<Supplier>();
        }
    }
}

The entity class definitions for the Northwind database are all present in the generated code as well, with each table having an entity class defined for it. The entity classes are indicated by the Table attribute with no parameters. This means that the name of the entity class matches the table name:

[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.Customers")]
public partial class Customer : INotifyPropertyChanging, INotifyPropertyChanged
{

    private static PropertyChangingEventArgs emptyChangingEventArgs = new
PropertyChangingEventArgs(String.Empty);

    private string _CustomerID;

    private string _CompanyName;

    private string _ContactName;

    private string _ContactTitle;

    private string _Address;

    private string _City;

    private string _Region;

    private string _PostalCode;

    private string _Country;

    private string _Phone;

    private string _Fax;

    private EntitySet<CustomerCustomerDemo> _CustomerCustomerDemos;

    private EntitySet<Order> _Orders;

#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnCustomerIDChanging(string value);
partial void OnCustomerIDChanged();
partial void OnCompanyNameChanging(string value);
partial void OnCompanyNameChanged();
partial void OnContactNameChanging(string value);
partial void OnContactNameChanged();
partial void OnContactTitleChanging(string value);
partial void OnContactTitleChanged();
partial void OnAddressChanging(string value);
partial void OnAddressChanged();
partial void OnCityChanging(string value);
partial void OnCityChanged();
partial void OnRegionChanging(string value);
partial void OnRegionChanged();
partial void OnPostalCodeChanging(string value);
partial void OnPostalCodeChanged();
partial void OnCountryChanging(string value);
partial void OnCountryChanged();
partial void OnPhoneChanging(string value);
partial void OnPhoneChanged();
partial void OnFaxChanging(string value);
partial void OnFaxChanged();
#endregion

    public Customer()
    {
        this._CustomerCustomerDemos = new EntitySet<CustomerCustomerDemo>(new
Action<CustomerCustomerDemo>(this.attach_CustomerCustomerDemos), new
Action<CustomerCustomerDemo>(this.detach_CustomerCustomerDemos));
        this._Orders = new EntitySet<Order>(
            new Action<Order>(this.attach_Orders), new
Action<Order>(this.detach_Orders));
        OnCreated();
    }

    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void SendPropertyChanging()
    {
        if ((this.PropertyChanging != null))
        {
            this.PropertyChanging(this, emptyChangingEventArgs);
        }
    }

    protected virtual void SendPropertyChanged(String propertyName)
    {
        if ((this.PropertyChanged != null))
        {
            this.PropertyChanged(this,
                new PropertyChangedEventArgs(propertyName));
        }
    }

The standard property change notifications are implemented via INotifyPropertyChanging and INotifyPropertyChanged and have PropertyChanging and PropertyChanged events for conveying the change to a property. There is also a set of partial methods that will report when a specific property is modified on this entity class if they are implemented in another partial class definition for the entity class.

Note

Many of the classes generated by Microsoft .NET are generated as partial classes. This is so that you can extend them in your own partial class and add methods and properties to the class without being in danger of the code generator stomping on your code the next time it is regenerated.

In this case, if no other partial class definition is found, the compiler will remove those notifications. Partial methods enable the declaration of a method signature in one file of a partial class declaration and the implementation of the method in another. If the signature is found but the implementation is not, the signature is removed by the compiler.

The properties in the entity class match up to the columns in the database via the Column attribute, where the Name value is the database column name and the Storage value is the internal storage for the class of the data. Events for the property changes are wired into the setter for the property:

[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CompanyName",
DbType="NVarChar(40) NOT NULL", CanBeNull=false)]
public string CompanyName
{
    get
    {
        return this._CompanyName;
    }
    set
    {
        if ((this._CompanyName != value))
        {
            this.OnCompanyNameChanging(value);
            this.SendPropertyChanging();
            this._CompanyName = value;
            this.SendPropertyChanged("CompanyName");
            this.OnCompanyNameChanged();
        }
    }
}

For a one-to-many child relationship, an EntitySet<T> of the child entity class is declared with an Association attribute. The Association attribute specifies the relationship information between the parent and child entity classes, as shown here for the Orders property on Customer:

 [global::System.Data.Linq.Mapping.AssociationAttribute(Name="Customer_Order",
Storage="_Orders", ThisKey="CustomerID", OtherKey="CustomerID")]
 public EntitySet<Order> Orders
 {
     get
     {
         return this._Orders;
     }
     set
     {
         this._Orders.Assign(value);
     }
 }

LINQ to SQL covers much more than what has been shown here, and we encourage you to investigate it more. Now, however, let’s move on to the other data domain we are dealing with: LINQ to XML.

LINQ to XML is not only how you perform queries against XML, it is also a more developer-friendly way to work with XML. One of the main classes in LINQ to XML is XElement, which allows you to create XML in a manner that more closely resembles the structure of the XML itself. This may not seem like a big deal, but when you can see the XML taking shape in your code, it’s easier to know where you are. (Ever forget which XmlWriter.WriteEndElement you were on? We have!) You can get more details and examples about using XElement in Chapter 10, so we won’t go much further into it here, but as you can see, it is very easy to build up XML in a query.

The first part of the query deals with setting up the main XML element, BigSpenders, and getting the initial set of customers where the contact is the owner:

var bigSpenders = new XElement("BigSpenders",
            from top5 in
            (
                (from customer in
                     (
                         from c in dataContext.Customers
                         // get the customers where the contact is the owner
                         // and they placed orders
                         where c.ContactTitle.Contains("Owner")
                            && c.Orders.Count > 0

The middle of the query deals with joining the order and order detail information with the customer information to get the NetOrderTotal for the order. It also creates order data containing that value, the customer and order IDs, and the company name. We need the NetOrderTotal in the last part of the query, so stay tuned!

    join orderData in
        (
            from c in dataContext.Customers
            // get the customers where the contact is the owner
            // and they placed orders
            where c.ContactTitle.Contains("Owner")
               && c.Orders.Count > 0
            from o in c.Orders
            // get the order details
            join od in dataContext.OrderDetails
                on o.OrderID equals od.OrderID
            select new
            {
                c.CompanyName,
                c.CustomerID,
                o.OrderID,
                // have to calc order value from orderdetails
                //(UnitPrice*Quantity as Total)
                  (Total*Discount)
                // as NetOrderTotal
                NetOrderTotal = (
(((double)od.UnitPrice) * od.Quantity) -
((((double)od.UnitPrice) * od.Quantity) * od.Discount))
            }
        )
    on c.CustomerID equals orderData.CustomerID
    into customerOrders

The last part of the query determines the TotalSpend for that customer across all orders using the Sum function on NetOrderTotal for the generated customerOrders collection. Finally, the query selects only the top five customers with a TotalSpend value greater than 10,000 by using the Take function. (Take is the equivalent to TOP in SQL.) We then use those records to construct one inner Customer element with attributes that nest inside the BigSpenders root element we set up in the first part of the query:

             select new
             {
                  c.CompanyName,
                  c.ContactName,
                  c.Phone,
                  // Get the total amount spent by the customer
                  TotalSpend = customerOrders.Sum(order => order. NetOrderTotal)
             }
          )
      // only worry about customers that spent > 10000
      where customer.TotalSpend > 10000
      orderby customer.TotalSpend descending
     // only take the top 5 spenders
     select customer).Take(5)
)
// format the data as XML
select new XElement("Customer",
           new XAttribute("companyName", top5.CompanyName),
           new XAttribute("contactName", top5.ContactName),
           new XAttribute("phoneNumber", top5.Phone),
           new XAttribute("amountSpent", top5.TotalSpend)));
Note

It is much easier to build large-nested queries as individual queries first and then put them together once you are sure the inner query is working.

At this point, for all of the code here, nothing has happened yet. That’s right: until the query is accessed, nothing happens because of the magic of deferred execution. LINQ has constructed a query expression, but nothing has talked to the database; there is no XML in memory, nada. Once the WriteTo method is called on the bigSpenders query expression, the query is evaluated by LINQ to SQL, and the XML is constructed. The WriteTo method writes out the constructed XML to the XmlWriter provided, and we are done:

using (XmlWriter writer = XmlWriter.Create("BigSpenders.xml"))
{
    bigSpenders.WriteTo(writer);
}

If you are interested in what that SQL will look like, connect the DataContext.Log property to a TextWriter (like the console):

// Log the generated SQL to the console
dataContext.Log = Console.Out;

This query generates SQL that looks like this:

    Generated SQL for query - output via DataContext.Log
    SELECT [t10].[CompanyName], [t10].[ContactName], [t10].[Phone], 
        [t10].[TotalSpend]
    FROM (
        SELECT TOP (5) [t0].[Company Name] AS [CompanyName], 
            [t0].[Contact Name] AS
    [ContactName], [t0].[Phone], [t9].[value] AS [TotalSpend]
        FROM [Customers] AS [t0]
        OUTER APPLY (
            SELECT COUNT(*) AS [value]
            FROM [Orders] AS [t1]
            WHERE [t1].[Customer ID] = [t0].[Customer ID]
            ) AS [t2]
        OUTER APPLY (
            SELECT SUM([t8].[value]) AS [value]
            FROM (
                SELECT [t3].[Customer ID], [t6].[Order ID],
                     ([t7].[Unit Price] *
                     (CONVERT(Decimal(29,4),[t7].[Quantity]))) -
                         ([t7].[Unit Price] *
                         (CONVERT(Decimal(29,4),[t7].[Quantity])) *
                             (CONVERT(Decimal(29,4),[t7].[Discount]))) AS 
                                 [value],
                     [t7].[Order ID] AS [Order ID2],
                     [t3].[Contact Title] AS [ContactTitle],
                     [t5].[value] AS [value2],
                     [t6].[Customer ID] AS [CustomerID]
            FROM [Customers] AS [t3]
            OUTER APPLY (
                SELECT COUNT(*) AS [value]
                FROM [Orders] AS [t4]
                WHERE [t4].[Customer ID] = [t3].[Customer ID]
                ) AS [t5]
            CROSS JOIN [Orders] AS [t6]
            CROSS JOIN [Order Details] AS [t7]
            ) AS [t8]
        WHERE ([t0].[Customer ID] = [t8].[Customer ID]) AND ([t8].[Order ID] = [
t8].[Order ID2]) AND ([t8].[ContactTitle] LIKE @p0) AND ([t8].[value2] > @p1) AN
D ([t8].[CustomerID] = [t8].[Customer ID])
            ) AS [t9]
        WHERE ([t9].[value] > @p2) AND ([t0].[Contact Title] LIKE @p3) AND 
            ([t2].[va
    lue] > @p4)
        ORDER BY [t9].[value] DESC
        ) AS [t10]
    ORDER BY [t10].[TotalSpend] DESC
    -- @p0: Input String (Size = 0; Prec = 0; Scale = 0) [%Owner%]
    -- @p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [0]
    -- @p2: Input Decimal (Size = 0; Prec = 29; Scale = 4) [10000]
    -- @p3: Input String (Size = 0; Prec = 0; Scale = 0) [%Owner%]
    -- @p4: Input Int32 (Size = 0; Prec = 0; Scale = 0) [0]
    -- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.20706.1

Here is the final XML:

<BigSpenders>
  <Customer companyName="Folk och fa HB" contactName="Maria Larsson"
            phoneNumber="0695-34 67 21" amountSpent="39805.162472039461" />
  <Customer companyName="White Clover Markets" contactName="Karl Jablonski"
            phoneNumber="(206) 555-4112" amountSpent="35957.604972146451" />
  <Customer companyName="Bon app'" contactName="Laurence Lebihan"
            phoneNumber="91.24.45.40" amountSpent="22311.577472746558" />
  <Customer companyName="LINO-Delicateses" contactName="Felipe Izquierdo"
            phoneNumber="(8) 34-56-12" amountSpent="20458.544984650609" />
  <Customer companyName="Simons bistro" contactName="Jytte Petersen"
            phoneNumber="31 12 34 56" amountSpent="18978.777493602414" />
</BigSpenders>

See Also

The “The Three Parts of a LINQ Query,” “DataContext.Log, property,” “DataContext class,” “XElement class,” and “LINQ to SQL” topics in the MSDN documentation.

4.9 Being Selective About Your Query Results

Problem

You want to be able to get a dynamic subset of a query result.

Solution

Use the TakeWhile extension method to retrieve all results until the criteria are matched:

NorthwindEntities dataContext = new NorthwindEntities();

// find the products for all suppliers
var query =
    dataContext.Suppliers.GroupJoin(dataContext.Products,
        s => s.SupplierID, p => p.SupplierID,
        (s, products) => new
        {
            s.CompanyName,
            s.ContactName,
            s.Phone,
            Products = products
        }).OrderByDescending(supplierData => supplierData.Products.Count());

var results =
    query.AsEnumerable().TakeWhile(supplierData => 
        supplierData.Products.Count() > 3);
Console.WriteLine($"Suppliers that provide more than three products: " +
    $"{results.Count()}");
foreach (var supplierData in results)
{
    Console.WriteLine($"  Company Name : {supplierData.CompanyName}");
    Console.WriteLine($"  Contact Name : {supplierData.ContactName}");
    Console.WriteLine($"  Contact Phone : {supplierData.Phone}");
    Console.WriteLine($"  Products Supplied : {supplierData.Products.Count()}");
    foreach (var productData in supplierData.Products)
        Console.WriteLine($"        Product: {productData.ProductName}");
}

You can also use the SkipWhile extension method to retrieve all results once the criteria are matched:

NorthwindEntities dataContext = new NorthwindEntities();

// find the products for all suppliers
var query =
    dataContext.Suppliers.GroupJoin(dataContext.Products,
        s => s.SupplierID, p => p.SupplierID,
        (s, products) => new
        {
            s.CompanyName,
            s.ContactName,
            s.Phone,
            Products = products
        }).OrderByDescending(supplierData => supplierData.Products.Count());

var results =
    query.AsEnumerable().SkipWhile(supplierData => 
    supplierData.Products.Count() > 3);
Console.WriteLine($"Suppliers that provide more than three products: " +
    $"{results.Count()}");
foreach (var supplierData in results)
{
    Console.WriteLine($"    Company Name : {supplierData.CompanyName}");
    Console.WriteLine($"    Contact Name : {supplierData.ContactName}");
    Console.WriteLine($"    Contact Phone : {supplierData.Phone}");
    Console.WriteLine($"    Products Supplied : {supplierData.Products.Count()}");
    foreach (var productData in supplierData.Products)
        Console.WriteLine($"        Product: {productData.ProductName}");
}

Discussion

In this example using LINQ to Entities, we determine the number of products each supplier provides, and sort the result set in descending order by product count:

var query =
    dataContext.Suppliers.GroupJoin(dataContext.Products,
        s => s.SupplierID, p => p.SupplierID,
        (s, products) => new
        {
            s.CompanyName,
            s.ContactName,
            s.Phone,
            Products = products
        }).OrderByDescending(supplierData => supplierData.Products.Count());

From that result, the supplier data is accepted into the final result set only if the supplier provides more than three products and the results are displayed. TakeWhile is used with a lambda expression to determine if the product count is greater than 3, and if so, the supplier is accepted into the result set:

var results = query.AsEnumerable().TakeWhile(supplierData =>
        supplierData.Products.Count() > 3);

If SkipWhile were used instead, all of the suppliers that provide three or fewer products would be returned:

var results = query.AsEnumerable().SkipWhile(supplierData =>
        supplierData.Products.Count() > 3);

Being able to write code-based conditions allows for more flexibility than the regular Take and Skip methods, which are based on absolute record count, but keep in mind that once the condition is hit for either TakeWhile or SkipWhile, you get all records after that, which is why it’s important to sort the result set before using them.

The query also uses GroupJoin, which is comparable to a SQL LEFT or RIGHT OUTER JOIN, but the result is not flattened. GroupJoin produces a hierarchical result set instead of a tabular one, which is used to get the collection of products by supplier in this example:

dataContext.Suppliers.GroupJoin(dataContext.Products,
   s => s.SupplierID, p => p.SupplierID,

This is the output for the TakeWhile:

Suppliers that provide more than three products: 4
    Company Name : Pavlova, Ltd.
    Contact Name : Ian Devling
    Contact Phone : (03) 444-2343
    Products Supplied : 5
        Product: Pavlova
        Product: Alice Mutton
        Product: Carnarvon Tigers
        Product: Vegie-spread
        Product: Outback Lager
    Company Name : Plutzer Lebensmittelgroßmärkte AG
    Contact Name : Martin Bein
    Contact Phone : (069) 992755
    Products Supplied : 5
        Product: Rössle Sauerkraut
        Product: Thüringer Rostbratwurst
        Product: Wimmers gute Semmelknödel
        Product: Rhönbräu Klosterbier
        Product: Original Frankfurter grüne Soße
    Company Name : New Orleans Cajun Delights
    Contact Name : Shelley Burke
    Contact Phone : (100) 555-4822
    Products Supplied : 4
        Product: Chef Anton's Cajun Seasoning
        Product: Chef Anton's Gumbo Mix
        Product: Louisiana Fiery Hot Pepper Sauce
        Product: Louisiana Hot Spiced Okra
    Company Name : Specialty Biscuits, Ltd.
    Contact Name : Peter Wilson
    Contact Phone : (161) 555-4448
    Products Supplied : 4
        Product: Teatime Chocolate Biscuits
        Product: Sir Rodney's Marmalade
        Product: Sir Rodney's Scones
        Product: Scottish Longbreads

See Also

The “Enumerable.TakeWhile method,” “Enumerable.SkipWhile method,” and “Enumerable.GroupJoin method” topics in the MSDN documentation.

4.10 Using LINQ with Collections That Don’t Support IEnumerable<T>

Problem

There are a whole bunch of collections that don’t support the generic versions of IEnumerable or ICollection but that do support the original nongeneric versions of the IEnumerable or ICollection interfaces, and you would like to be able to query those collections using LINQ.

Solution

The type cannot be inferred from the original IEnumeration or ICollection interfaces, so you must provide it using either the OfType<T> or Cast<T> extension methods or by specifying the type in the from clause, which inserts a Cast<T> for you. The first example uses Cast<XmlNode> to let LINQ know that the elements in the XmlNodeList returned from XmlDocument.SelectNodes are of type XmlNode. For an example of how to use the OfType<T> extension method, see the Discussion section:

// Make some XML with some types that you can use with LINQ
// that don't support IEnumerable<T> directly
XElement xmlFragment = new XElement("NonGenericLinqableTypes",
                        new XElement("IEnumerable",
                            new XElement("System.Collections",
                                new XElement("ArrayList"),
                                new XElement("BitArray"),
                                new XElement("Hashtable"),
                                new XElement("Queue"),
                                new XElement("SortedList"),
                                new XElement("Stack")),
                            new XElement("System.Net",
                                new XElement("CredentialCache")),
                            new XElement("System.Xml",
                                new XElement("XmlNodeList")),
                            new XElement("System.Xml.XPath",
                                new XElement("XPathNodeIterator"))),
                        new XElement("ICollection",
                            new XElement("System.Diagnostics",
                                new XElement("EventLogEntryCollection")),
                            new XElement("System.Net",
                                new XElement("CookieCollection")),
                            new XElement("System.Security.AccessControl",
                                new XElement("GenericAcl")),
                            new XElement("System.Security",
                                new XElement("PermissionSet"))));

XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlFragment.ToString());

// Select the names of the nodes under IEnumerable that have children and are
// named System.Collections and contain a capital S and return that list in
// descending order
var query =
from node in
    doc.SelectNodes("/NonGenericLinqableTypes/IEnumerable/*").Cast<XmlNode>()
    where node.HasChildNodes &&
        node.Name == "System.Collections"
    from XmlNode xmlNode in node.ChildNodes
    where xmlNode.Name.Contains('S')
    orderby xmlNode.Name descending
    select xmlNode.Name;

foreach (string name in query)
    Console.WriteLine(name);

The second example works against the application event log and retrieves the errors that occurred in the last six hours. The type of the element in the collection (EventLogEntry) is provided next to the from keyword, which allows LINQ to infer the rest of the information it needs about the collection element type:

EventLog log = new EventLog("Application");
query = from EventLogEntry entry in log.Entries
        where entry.EntryType == EventLogEntryType.Error &&
            entry.TimeGenerated > DateTime.Now.Subtract(new TimeSpan(6, 0, 0))
        select entry.Message;

Console.WriteLine($"There were {query.Count<string>()}" +
    " Application Event Log error messages in the last 6 hours!");
foreach (string message in query)
    Console.WriteLine(message);

Discussion

Cast<T> will transform the IEnumerable into IEnumerable<T> so that LINQ can access each item in the collection in a strongly typed manner. Before you use Cast<T>, it would behoove you to check that all elements of the collection really are of type T; otherwise, you will get an InvalidCastException if the type of the element is not convertible to the type T specified, because all elements will be cast using that type. Placing the type of the element next to the from keyword acts just like a Cast<T>:

ArrayList stuff = new ArrayList();
stuff.Add(DateTime.Now);
stuff.Add(DateTime.Now);
stuff.Add(1);
stuff.Add(DateTime.Now);

var expr = from item in stuff.Cast<DateTime>()
            select item;
foreach (DateTime item in expr)
    Console.WriteLine(item);
Note

Because of the deferred execution semantics, the exception that occurs with Cast<T> or from happens only once that element has been iterated to.

Another way to approach this issue is to use OfType<T>, as it will return only the elements of a specific type and not try to cast elements from one type to another:

var expr = from item in stuff.OfType<DateTime>()
            select item;
// only three elements, all DateTime returned. No exceptions
foreach (DateTime item in expr)
    Console.WriteLine(item);

See Also

The “OfType<TResult> method” and “Cast<TResult> method” topics in the MSDN documentation.

4.12 Using Lambda Expressions

Problem

C# includes a feature called lambda expressions. While you can view lambda expressions as syntactic sugar for making anonymous method definition less difficult, you also want to understand all of the different ways that you can use them to help you in your daily programming chores as well as the ramifications of those uses.

Solution

Lambda expressions can be implemented by the compiler from methods created by the developer. There are two orthogonal characteristics that lambda expressions may have:

  • Parameter lists may have explicit or implicit types.

  • Bodies may be expressions or statement blocks.

Let’s start with the original way to use delegates. First, you would declare a delegate type—DoWork in this case—and then create an instance of it (as shown here in the WorkItOut method). Declaring the instance of the delegate requires that you specify a method to execute when the delegate is invoked, and here the DoWorkMethodImpl method has been connected. The delegate is invoked, and the text is written to the console via the DoWorkMethodImpl method:

class OldWay
{
    // declare delegate
    delegate int DoWork(string work);

    // have a method to create an instance of and call the delegate
    public void WorkItOut()
    {
        // declare instance
        DoWork dw = new DoWork(DoWorkMethodImpl);
        // invoke delegate
        int i = dw("Do work the old way");
    }

    // Have a method that the delegate is tied to with a matching signature
    // so that it is invoked when the delegate is called
    public int DoWorkMethodImpl(string s)
    {
        Console.WriteLine(s);
        return s.GetHashCode();
    }
}

Lambda expressions allow you to set up code to run when a delegate is invoked, but you do not need to give a named formal method declaration to the delegate. The method thus declared is nameless and closed over the scope of the outer method. For example, you could have written the preceding code using a lambda expression such as this:

class LambdaWay
{
    // declare delegate
    delegate int DoWork(string work);

    // have a method to create an instance of and call the delegate
    public void WorkItOut()
    {
        // declare instance
        DoWork dw = s =>
        {
            Console.WriteLine(s);
            return s.GetHashCode();
        };
        // invoke delegate
        int i = dw("Do some inline work");
    }
}

Notice that instead of having a method called DoWorkMethodImpl, you use the => operator to directly assign the code from that method inline to the DoWork delegate. The assignment looks like this:

DoWork dw = s =>
{
    Console.WriteLine(s);
    return s.GetHashCode();
};

You also provide the parameter required by the DoWork delegate (string), and your code returns an int (s.GetHashCode()) as the delegate requires. When you’re setting up a lambda expression, the code must match the delegate signature, or you will get a compiler error.

By “match,” we mean:

  • If explicitly typed, the lambda parameters must exactly match the delegate parameters. If implicitly typed, the lambda parameters get the delegate parameter types.

  • The body of the lambda must be a legal expression or statement block given the parameter types.

  • The return type of the lambda must be implicitly convertible to the return type of the delegate. It need not match exactly.

There is yet another way you can set up the delegate: through the magic of delegate inference. Delegate inference allows you to assign the method name directly to the delegate instance without having to write the code to create a new delegate object. Under the covers, C# actually writes the IL for creating the delegate object, but you don’t have to do it explicitly here. Using delegate inference instead of writing out new [Delegate Type]([Method Name]) everywhere helps to unclutter the code involved in delegate use, as shown here:

class DirectAssignmentWay
{
    // declare delegate
    delegate int DoWork(string work);

    // have a method to create an instance of and call the delegate
    public void WorkItOut()
    {
        // declare instance and assign method
        DoWork dw = DoWorkMethodImpl;
        // invoke delegate
        int i = dw("Do some direct assignment work");
    }
    // Have a method that the delegate is tied to with a matching signature
    // so that it is invoked when the delegate is called
    public int DoWorkMethodImpl(string s)
    {
        Console.WriteLine(s);
        return s.GetHashCode();
    }
}

Notice that all that is assigned to the DoWork delegate instance dw is the method name DoWorkMethodImpl. There is no new DoWork(DoWorkMethodImpl) call as there was in older C# code.

Note

Remember, the underlying delegate wrapper does not go away; delegate inference just simplifies the syntax a bit by hiding some of it.

Alternatively, you can also set up lambda expressions that take generic type parameters to enable working with generic delegates, as you see here in the GenericWay class:

class GenericWay
{
    // have a method to create two instances of and call the delegates
    public void WorkItOut()
    {
        Func<string, string> dwString = s =>
        {
            Console.WriteLine(s);
            return s;
        };

        // invoke string delegate
        string retStr = dwString("Do some generic work");

        Func<int, int> dwInt = i =>
        {
            Console.WriteLine(i);
            return i;
        };

        // invoke int delegate
        int j = dwInt(5);

    }
}

Discussion

One of the useful things about lambda expressions is the concept of outer variables. The official definition of outer variable is any local variable, value parameter, or parameter array with a scope that contains the lambda expression.

This means that, inside the code of the lambda expression, you can touch variables outside the scope of that method. This introduces the concept of “capturing” the variables, which occurs when a lambda expression actually makes reference to one of the outer variables. In the following example, the count variable is captured and incremented by the lambda expression. The count variable is not part of the original scope of the lambda expression but rather part of the outer scope. It is incremented, and then the incremented value is returned and totaled:

public void SeeOuterWork()
{
    int count = 0;
    int total = 0;
    Func<int> countUp = () => count++;
    for (int i = 0; i < 10; i++)
        total += countUp();
    Debug.WriteLine($"Total = {total}");
}

What capturing actually does is extend the lifetime of the outer variable to coincide with the lifetime of the underlying delegate instance that represents the lambda expression. This should encourage you to be careful about what you touch from inside a lambda expression. You could be causing things to hang around a lot longer than you originally planned. The garbage collector won’t get a chance to clean up those outer variables until later, when they are used in the lambda expression. Capturing outer variables has another garbage-collector effect: when locals or value parameters are captured, they are no longer considered to be fixed but are now movable, so any unsafe code must now fix that variable—via the fixed keyword—before the variable is used.

Outer variables can affect how the compiler generates the internal IL for the lambda expression. If the lambda expression uses outer variables, the lambda expression is generated as a private method of a nested class. If the lambda expression does not use outer variables, it would be generated as another private method of the class in which it is declared. If the outer method is static, then the lambda expression cannot access instance members via the this keyword, as the nested class will also be generated as static.

There are two types of lambda expressions: expression lambdas and statement lambdas. This expression lambda has no parameters and simply increments the count variable in an expression:

int count = 0;
Func<int> countUp = () => count++;

Statement lambdas have the body enclosed in curly braces and can contain any number of statements like this:

Func<int, int> dwInt = i =>
{
    Console.WriteLine(i);
    return i;
};
Note

A few last things to remember about lambda expressions:

  • They can’t use break, goto, or continue to jump from the lambda expression to a target outside the lambda expression block.

  • No unsafe code can be executed inside a lambda expression.

  • Lambda expressions cannot be used on the left side of the is operator.

  • Since lambda expressions are a superset of anonymous methods, all restrictions that apply to anonymous methods also apply to lambda expressions.

See Also

The “Lambda Expressions (C# Programming Guide)” topic in the MSDN documentation.

4.13 Using Different Parameter Modifiers in Lambda Expressions

Problem

You know you can pass parameters to lambda expressions, but you need to figure out what parameter modifiers are valid with them.

Solution

Lambda expressions can use out and ref parameter modifiers but not the params modifier in their parameter list. However, this does not prevent the creation of delegates with any of these modifiers, as shown here:

// declare out delegate
delegate int DoOutWork(out string work);

// declare ref delegate
delegate int DoRefWork(ref string work);

// declare params delegate
delegate int DoParamsWork(params object[] workItems);

Even though the DoParamsWork delegate is defined with the params keyword on the parameter, it can still be used as a type for a lambda expression, as you’ll see in a bit. To use the DoOutWork delegate, create a lambda expression inline using the out keyword and assign it to the DoOutWork delegate instance. Inside the lambda expression body, the out variable s is assigned a value first (as it doesn’t have one by definition as an out parameter), writes it to the console, and returns the string hash code. Note that in the parameter list, you must provide the type of s (i.e., string), as type is not inferred for variables marked with the out or ref keywords. It is not inferred for out or ref variables to preserve the representation at the call site and the parameter declaration site to help the developer clearly reason about the possible assignment to these variables:

// declare instance and assign method
DoOutWork dow = (out string s) =>
{
    s = "WorkFinished";
    Console.WriteLine(s);
    return s.GetHashCode();
};

To run the lambda expression code, invoke the delegate with an out parameter, and then print out the result to the console:

// invoke delegate
string work;
int i = dow(out work);
Console.WriteLine(work);

To use the ref parameter modifier in a lambda expression, create an inline method to hook up to the DoRefWork delegate with a ref parameter. In the method, you write the original value out, reassign the value, and get the hash code of the new value. Remember that, as with the out keyword, you must provide the type of s (string) in the parameter list, as type cannot be inferred for a variable marked with the ref keyword:

// declare instance and assign method
DoRefWork drw = (ref string s) =>
{
    Console.WriteLine(s);
    s = "WorkFinished";
    return s.GetHashCode();
};

To run the lambda expression, assign a value to the string work and then pass it as a ref parameter to the DoRefWork delegate that is instantiated. Upon the return from the delegate call, write out the new value for the work string:

// invoke delegate
work = "WorkStarted";
i = drw(ref work);
Console.WriteLine(work);

While it is possible to declare a delegate with the params modifier, you cannot hook up the delegate using a lambda expression with the params keyword in the parameter list. If you try this, the compiler displays the CS1670 params is not valid in this context compiler error on the DoParamsWork line:

////Done as an lambda expression you also get
////CS1670 "params is not valid in this context"
//DoParamsWork dpwl = (params object[] workItems) =>
//{
//    foreach (object o in workItems)
//    {
//        Console.WriteLine(o.ToString());
//    }
//    return workItems.GetHashCode();
//};

Even if you attempt this using an anonymous method instead of a lambda expression, you still cannot hook up this delegate with the params keyword in the parameter list. If you try, the compiler still displays the CS1670 params is not valid in this context compiler error on the DoParamsWork line:

//Done as an anonymous method you get CS1670 
    "params is not valid in this context"
//DoParamsWork dpwa = delegate (params object[] workItems)
//{
//    foreach (object o in workItems)
//    {
//        Console.WriteLine(o.ToString());
//    }
//    return workItems.GetHashCode();
//};

You can, however, omit the params keyword and still set up the lambda expression for the delegate, as shown here:

// All we have to do is omit the params keyword.
DoParamsWork dpw = workItems =>
{
    foreach (object o in workItems)
        Console.WriteLine(o.ToString());
    return workItems.GetHashCode();
};

Notice that although you’ve removed the params keyword from the lambda expression, this doesn’t stop you from using the same syntax. The params keyword is present on the delegate type, so you can invoke it thusly:

int i = dpw("Hello", "42", "bar");

So this illustrates that you can bind a lambda expression to a delegate declared using params, and once you’ve done that, you can invoke the lambda expression, passing in any number of parameters you like, just as you’d expect.

Discussion

Lambda expressions cannot access the ref or out parameters of an outer scope. This means any out or ref variables that were defined as part of the containing method are off-limits for use inside the body of the lambda expression:

public void TestOut(out string outStr)
{
    // declare instance
    DoWork dw = s =>
    {
        Console.WriteLine(s);
        // Causes error CS1628:
        // "Cannot use ref or out parameter 'outStr' inside an
        // anonymous method, lambda expression, or query expression"
        outStr = s;
        return s.GetHashCode();
    };
    // invoke delegate
    int i = dw("DoWorkMethodImpl1");
}

public void TestRef(ref string refStr)
{
    // declare instance
    DoWork dw = s =>
    {
        Console.WriteLine(s);
        // Causes error CS1628:
        // "Cannot use ref or out parameter 'refStr' inside an
        // anonymous method, lambda expression, or query expression"
        refStr = s;
        return s.GetHashCode();
    };
    // invoke delegate
    int i = dw("DoWorkMethodImpl1");
}

Interestingly enough, lambda expressions can access outer variables with the params modifier:

// declare delegate
delegate int DoWork(string work);

public void TestParams(params string[] items)
{
    // declare instance
    DoWork dw = s =>
    {
        Console.WriteLine(s);
        foreach (string item in items)
            Console.WriteLine(item);
        return s.GetHashCode();
    };
    // invoke delegate
    int i = dw("DoWorkMethodImpl1");
}

Because the params modifier is there for the benefit of the calling site (so the compiler knows to make this a method call that supports variable-length argument lists) and because lambda expressions are never called directly (they’re always called via a delegate), it makes no sense for a lambda expression to be decorated with something there for the benefit of the calling site—as there is no calling site. This is why it doesn’t matter that you can’t use the params keyword on a lambda expression. For lambda expressions, the calling site is always calling through the delegate, so what matters is whether that delegate has the params keyword or not.

See Also

Recipe 1.17; the “CS1670,” “CS1525,” “CS1628,” “out,” “ref,” “params,” and “System.ParamArrayAttribute” topics in the MSDN documentation.

4.14 Speeding Up LINQ Operations with Parallelism

Problem

You have a LINQ query that performs an expensive operation that slows down the processing, and you would like to speed it up.

Solution

Use PLINQ (Parallel LINQ) to utilize the full capacities of your machine to process the query faster.

To demonstrate this, let’s consider the plight of Brooke and Katie. Brooke and Katie are working on a cookbook together and they need to evaluate all of the recipes for all of the chapters. Since there are so many recipes, they want to be able to hand off the rudimentary validation steps for the recipes and then Brooke or Katie gets a final pass at each recipe as the main editor for final fit and finish.

Each Chapter has a number of Recipes in it, and the Recipe validation steps are:

  1. Read the text of the recipe for premise.

  2. Check the recipe accuracy of ingredients and measurements.

  3. Prepare the recipe and taste once for each rank of difficulty for the recipe.

  4. Have Brooke or Katie perform the final editing pass.

If any stage of the recipe evaluation fails, that stage needs to be redone unless it is the tasting stage. If a Recipe fails the tasting stage, it needs to start over.

To process the collection of RecipeChapters (chapters in the example) with regular LINQ, we could use the following statement:

chapters.Select(c => TimedEvaluateChapter(c, rnd)).ToList();

TimedEvaluateChapter is a method that performs the evaluation of the RecipeChapter and all of the Recipes in the RecipeChapter while timing the evaluation. EvaluateRecipe is called once for each Recipe in the RecipeChapter to perform the Recipe validation steps:

private static RecipeChapter TimedEvaluateChapter(RecipeChapter rc, Random rnd)
{
    Stopwatch watch = new Stopwatch();
    LogOutput($"Evaluating Chapter {rc}");
    watch.Start();
    foreach (var r in rc.Recipes)
        EvaluateRecipe(r, rnd);
    watch.Stop();
    LogOutput($"Finished Evaluating Chapter {rc}");
    return rc;
}

In order to process the Recipes faster, we add a call to the AsParallel extension method before we call Select to invoke TimedEvaluateChapter for each RecipeChapter:

chapters.AsParallel().Select(c => TimedEvaluateChapter(c, rnd)).ToList();

Your results will vary based on your hardware, but the following times were recorded on a run using regular LINQ and then subsequently PLINQ:

Full Chapter Evaluation with LINQ took: 00:01:19.1395258
Full Chapter Evaluation with PLINQ took: 00:00:25.1708103

Discussion

When you’re using PLINQ, the main thing to keep in mind is that the unit of work being parallelized must be significant enough to justify the cost of the parallelization. There are additional setup and teardown costs to doing operations in parallel (like partitioning of the data set) and if the data set is too small or the operation on each member of the set is not expensive enough to be helped by using parallel techniques, you could actually perform worse. If PLINQ determines that it cannot effectively parallelize the query, it will process it sequentially. If this happens, there are a number of additional methods you can use to adjust depending upon your particular situation (WithExecutionMode, WithDegreeOfParallelism).

As in all engineering, measuring your results is the key to understanding if you are improving or not, so with that in mind, we created the TimedEvaluateChapter method to call from our Select statement:

chapters.AsParallel().Select(c => TimedEvaluateChapter(c, rnd)).ToList();

TimedEvaluateChapter times the process of evaluating every Recipe in the RecipeChapter and wraps that value in calls to Stopwatch.Start and Stopwatch.Stop for timing. The timing results are then available in Stopwatch.Elapsed. Note that if you restart the Stopwatch without calling Stopwatch.Reset, the timer will add to the value already in the Stopwatch and you may get a bigger value than you expected:

private static RecipeChapter TimedEvaluateChapter(RecipeChapter rc, Random rnd)
{
    Stopwatch watch = new Stopwatch();
    LogOutput($"Evaluating Chapter {rc}");
    watch.Start();
    foreach (var r in rc.Recipes)
        EvaluateRecipe(r, rnd);
    watch.Stop();
    LogOutput($"Finished Evaluating Chapter {rc}");
    return rc;
}

EvaluateRecipe performs the validation steps on each recipe recursively until it passes the final edit from Brooke and Katie. Thread.Sleep is called to simulate work for each step:

private static Recipe EvaluateRecipe(Recipe r, Random rnd)
{
    //Recipe Editing steps
    if (!r.TextApproved)
    {
        //Read the recipe to make sure it makes sense
        Thread.Sleep(50);
        int evaluation = rnd.Next(1, 10);
        // 7 means it didn't make sense so don't approve it,
        // send it back for rework
        if (evaluation == 7)
        {
            LogOutput($"{r} failed the readthrough! Reworking...");
        }
        else
            r.TextApproved = true;
        return EvaluateRecipe(r, rnd);
    }
    else if (!r.IngredientsApproved)
    {
        //Check the ingredients and measurements
        Thread.Sleep(100);
        int evaluation = rnd.Next(1, 10);
        // 3 means the ingredients or measurements are incorrect,
        // send it back for rework
        if (evaluation == 3)
        {
            LogOutput($"{r} had incorrect measurements! Reworking...");
        }
        else
            r.IngredientsApproved = true;
        return EvaluateRecipe(r, rnd);
    }
    else if (r.RecipeEvaluated != r.Rank)
    {
        //Prepare recipe and taste
        Thread.Sleep(50 * r.Rank);
        int evaluation = rnd.Next(1, 10);
        // 4 means it didn't taste right, send it back for rework
        if (evaluation == 4)
        {
            r.TextApproved = false;
            r.IngredientsApproved = false;
            r.RecipeEvaluated = 0;
            LogOutput($"{r} tasted bad!  Reworking...");
        }
        else
            r.RecipeEvaluated++;
        return EvaluateRecipe(r, rnd);
    }
    else
    {
        //Final editing pass(Brooke or Katie)
        Thread.Sleep(50 * r.Rank);
        int evaluation = rnd.Next(1, 10);
        // 1 means it just wasn't quite ready, send it back for rework
        if (evaluation == 1)
        {
            r.TextApproved = false;
            r.IngredientsApproved = false;
            r.RecipeEvaluated = 0;
            LogOutput($"{r} failed final editing!  Reworking...");
            return EvaluateRecipe(r, rnd);
        }
        else
        {
            r.FinalEditingComplete = true;
            LogOutput($"{r} is ready for release!");
        }
    }
    return r;
}

Here are the definitions of the RecipeChapter and Recipe classes used to help Brooke and Katie evaluate all of the recipes:

public class RecipeChapter
{
    public int Number { get; set; }
    public string Title { get; set; }
    public List<Recipe> Recipes { get; set; }
    public override string ToString() => $"{Number} - {Title}";
}

public class Recipe
{
    public RecipeChapter Chapter { get; set; }
    public string MainIngredient { get; set; }
    public int Number { get; set; }
    public bool TextApproved { get; set; }
    public bool IngredientsApproved { get; set; }

    /// <summary>
    /// Recipe should be evaluated as many times as the Rank of the recipe
    /// </summary>
    public int RecipeEvaluated { get; set; }

    public bool FinalEditingComplete { get; set; }

    public int Rank { get; set; }

    public override string ToString() =>
        $"{Chapter.Number}.{Number} ({Chapter.Title}:{MainIngredient})";
}

Sample output from the LINQ run looks like this and processes the collection in sequential order:

Running Cookbook Evaluation
Evaluating Chapter 1 - Soups
1.1 (Soups:Sprouts, Mung Bean) is ready for release!
1.2 (Soups:Potato Bread) is ready for release!
1.3 (Soups:Chicken Liver) tasted bad!  Reworking...
1.3 (Soups:Chicken Liver) is ready for release!
1.4 (Soups:Cherimoya) tasted bad!  Reworking...
1.4 (Soups:Cherimoya) had incorrect measurements! Reworking...
1.4 (Soups:Cherimoya) is ready for release!
1.5 (Soups:High-Protein Bread) is ready for release!
1.6 (Soups:Flat Bread) failed the readthrough! Reworking...
1.6 (Soups:Flat Bread) is ready for release!
1.7 (Soups:Pomegranate) is ready for release!
1.8 (Soups:Carissa, Natal Plum) had incorrect measurements! Reworking...
1.8 (Soups:Carissa, Natal Plum) is ready for release!
1.9 (Soups:Ideal Flat Bread) is ready for release!
1.10 (Soups:Banana Bread) tasted bad!  Reworking...
1.10 (Soups:Banana Bread) is ready for release!
Finished Evaluating Chapter 1 - Soups
Evaluating Chapter 2 - Salads
2.1 (Salads:Caraway) tasted bad!  Reworking...
2.1 (Salads:Caraway) tasted bad!  Reworking...
2.1 (Salads:Caraway) had incorrect measurements! Reworking...
2.1 (Salads:Caraway) is ready for release!
2.2 (Salads:Potatoes, Red) had incorrect measurements! Reworking...
2.2 (Salads:Potatoes, Red) tasted bad!  Reworking...
2.2 (Salads:Potatoes, Red) is ready for release!
2.3 (Salads:Lemon) is ready for release!
2.4 (Salads:Cream cheese) is ready for release!
2.5 (Salads:Artichokes, Domestic) is ready for release!
2.6 (Salads:Grapefruit) is ready for release!
2.7 (Salads:Lettuce, Iceberg) is ready for release!
2.8 (Salads:Fenugreek) is ready for release!
2.9 (Salads:Ostrich) is ready for release!
2.10 (Salads:Brazil Nuts) tasted bad!  Reworking...
2.10 (Salads:Brazil Nuts) had incorrect measurements! Reworking...
2.10 (Salads:Brazil Nuts) tasted bad!  Reworking...
2.10 (Salads:Brazil Nuts) is ready for release!
Finished Evaluating Chapter 2 - Salads
Evaluating Chapter 3 - Appetizers
3.1 (Appetizers:Loquat) tasted bad!  Reworking...
3.1 (Appetizers:Loquat) had incorrect measurements! Reworking...
3.1 (Appetizers:Loquat) tasted bad!  Reworking...
3.1 (Appetizers:Loquat) is ready for release!
3.2 (Appetizers:Bergenost) is ready for release!
3.3 (Appetizers:Tomato Red Roma) had incorrect measurements! Reworking...
3.3 (Appetizers:Tomato Red Roma) tasted bad!  Reworking...
3.3 (Appetizers:Tomato Red Roma) tasted bad!  Reworking...
3.3 (Appetizers:Tomato Red Roma) is ready for release!
3.4 (Appetizers:Guava) failed final editing!  Reworking...
3.4 (Appetizers:Guava) is ready for release!
3.5 (Appetizers:Squash Flower) is ready for release!
3.6 (Appetizers:Radishes, Red) is ready for release!
3.7 (Appetizers:Goose Liver) tasted bad!  Reworking...
3.7 (Appetizers:Goose Liver) had incorrect measurements! Reworking...
3.7 (Appetizers:Goose Liver) is ready for release!
3.8 (Appetizers:Okra) had incorrect measurements! Reworking...
3.8 (Appetizers:Okra) is ready for release!
3.9 (Appetizers:Borage) is ready for release!
3.10 (Appetizers:Peppers) is ready for release!
Finished Evaluating Chapter 3 - Appetizers
Evaluating Chapter 4 - Entrees
4.1 (Entrees:Plantain) is ready for release!
4.2 (Entrees:Pignola (Pine)) is ready for release!
4.3 (Entrees:Potatoes, Gold) is ready for release!
4.4 (Entrees:Ribeye) failed the readthrough! Reworking...
4.4 (Entrees:Ribeye) is ready for release!
4.5 (Entrees:Sprouts, Mung Bean) failed the readthrough! Reworking...
4.5 (Entrees:Sprouts, Mung Bean) had incorrect measurements! Reworking...
4.5 (Entrees:Sprouts, Mung Bean) failed final editing!  Reworking...
4.5 (Entrees:Sprouts, Mung Bean) is ready for release!
4.6 (Entrees:Squash) had incorrect measurements! Reworking...
4.6 (Entrees:Squash) is ready for release!
4.7 (Entrees:Squash, Winter) tasted bad!  Reworking...
4.7 (Entrees:Squash, Winter) is ready for release!
4.8 (Entrees:Corn, Blue) is ready for release!
4.9 (Entrees:Snake) had incorrect measurements! Reworking...
4.9 (Entrees:Snake) tasted bad!  Reworking...
4.9 (Entrees:Snake) tasted bad!  Reworking...
4.9 (Entrees:Snake) is ready for release!
4.10 (Entrees:Prosciutto) is ready for release!
Finished Evaluating Chapter 4 - Entrees
Evaluating Chapter 5 - Desserts
5.1 (Desserts:Mushroom, White, Silver Dollar) tasted bad!  Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) had incorrect measurements!
Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) tasted bad!  Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) tasted bad!  Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) had incorrect measurements!
Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) is ready for release!
5.2 (Desserts:Eggplant) is ready for release!
5.3 (Desserts:Asparagus Peas) tasted bad!  Reworking...
5.3 (Desserts:Asparagus Peas) failed the readthrough! Reworking...
5.3 (Desserts:Asparagus Peas) failed the readthrough! Reworking...
5.3 (Desserts:Asparagus Peas) is ready for release!
5.4 (Desserts:Squash, Kabocha) failed the readthrough! Reworking...
5.4 (Desserts:Squash, Kabocha) tasted bad!  Reworking...
5.4 (Desserts:Squash, Kabocha) is ready for release!
5.5 (Desserts:Sprouts, Radish) is ready for release!
5.6 (Desserts:Mushroom, Black Trumpet) is ready for release!
5.7 (Desserts:Tea Cakes) tasted bad!  Reworking...
5.7 (Desserts:Tea Cakes) tasted bad!  Reworking...
5.7 (Desserts:Tea Cakes) failed the readthrough! Reworking...
5.7 (Desserts:Tea Cakes) is ready for release!
5.8 (Desserts:Blueberries) had incorrect measurements! Reworking...
5.8 (Desserts:Blueberries) tasted bad!  Reworking...
5.8 (Desserts:Blueberries) is ready for release!
5.9 (Desserts:Sago Palm) is ready for release!
5.10 (Desserts:Opossum) had incorrect measurements! Reworking...
5.10 (Desserts:Opossum) is ready for release!
Finished Evaluating Chapter 5 - Desserts
Evaluating Chapter 6 - Snacks
6.1 (Snacks:Cheddar) tasted bad!  Reworking...
6.1 (Snacks:Cheddar) is ready for release!
6.2 (Snacks:Melon, Bitter) is ready for release!
6.3 (Snacks:Scallion) is ready for release!
6.4 (Snacks:Squash Chayote) failed final editing!  Reworking...
6.4 (Snacks:Squash Chayote) is ready for release!
6.5 (Snacks:Roasted Turkey) is ready for release!
6.6 (Snacks:Lime) is ready for release!
6.7 (Snacks:Hazelnut) is ready for release!
6.8 (Snacks:Radishes, Daikon) tasted bad!  Reworking...
6.8 (Snacks:Radishes, Daikon) tasted bad!  Reworking...
6.8 (Snacks:Radishes, Daikon) failed the readthrough! Reworking...
6.8 (Snacks:Radishes, Daikon) tasted bad!  Reworking...
6.8 (Snacks:Radishes, Daikon) is ready for release!
6.9 (Snacks:Salami) failed the readthrough! Reworking...
6.9 (Snacks:Salami) is ready for release!
6.10 (Snacks:Mushroom, Oyster) failed the readthrough! Reworking...
6.10 (Snacks:Mushroom, Oyster) is ready for release!
Finished Evaluating Chapter 6 - Snacks
Evaluating Chapter 7 - Breakfast
7.1 (Breakfast:Daikon Radish) had incorrect measurements! Reworking...
7.1 (Breakfast:Daikon Radish) is ready for release!
7.2 (Breakfast:Lettuce, Red Leaf) failed final editing!  Reworking...
7.2 (Breakfast:Lettuce, Red Leaf) is ready for release!
7.3 (Breakfast:Alfalfa Sprouts) is ready for release!
7.4 (Breakfast:Tea Cakes) is ready for release!
7.5 (Breakfast:Chia seed) is ready for release!
7.6 (Breakfast:Tangerine) is ready for release!
7.7 (Breakfast:Spinach) is ready for release!
7.8 (Breakfast:Flank Steak) is ready for release!
7.9 (Breakfast:Loganberries) had incorrect measurements! Reworking...
7.9 (Breakfast:Loganberries) had incorrect measurements! Reworking...
7.9 (Breakfast:Loganberries) had incorrect measurements! Reworking...
7.9 (Breakfast:Loganberries) is ready for release!
7.10 (Breakfast:Opossum) is ready for release!
Finished Evaluating Chapter 7 - Breakfast
Evaluating Chapter 8 - Sandwiches
8.1 (Sandwiches:Rhubarb) tasted bad!  Reworking...
8.1 (Sandwiches:Rhubarb) is ready for release!
8.2 (Sandwiches:Pickle, Brine) is ready for release!
8.3 (Sandwiches:Oranges) tasted bad!  Reworking...
8.3 (Sandwiches:Oranges) had incorrect measurements! Reworking...
8.3 (Sandwiches:Oranges) is ready for release!
8.4 (Sandwiches:Chayote, Pipinella, Vegetable Pear) tasted bad!  Reworking...
8.4 (Sandwiches:Chayote, Pipinella, Vegetable Pear) is ready for release!
8.5 (Sandwiches:Bear) is ready for release!
8.6 (Sandwiches:Panela) had incorrect measurements! Reworking...
8.6 (Sandwiches:Panela) is ready for release!
8.7 (Sandwiches:Peppers, Red) had incorrect measurements! Reworking...
8.7 (Sandwiches:Peppers, Red) tasted bad!  Reworking...
8.7 (Sandwiches:Peppers, Red) failed the readthrough! Reworking...
8.7 (Sandwiches:Peppers, Red) failed the readthrough! Reworking...
8.7 (Sandwiches:Peppers, Red) had incorrect measurements! Reworking...
8.7 (Sandwiches:Peppers, Red) tasted bad!  Reworking...
8.7 (Sandwiches:Peppers, Red) is ready for release!
8.8 (Sandwiches:Oat Bread) is ready for release!
8.9 (Sandwiches:Peppers, Green) is ready for release!
8.10 (Sandwiches:Garlic) is ready for release!
Finished Evaluating Chapter 8 - Sandwiches
***********************************************
Full Chapter Evaluation with LINQ took: 00:01:19.1395258
***********************************************

Sample output from the PLINQ run looks like this, processes in parallel (note the evaluation of four RecipeChapters at the beginning), and processes items out of sequential order:

Evaluating Chapter 5 - Desserts
Evaluating Chapter 3 - Appetizers
Evaluating Chapter 1 - Soups
Evaluating Chapter 7 - Breakfast
7.1 (Breakfast:Daikon Radish) failed the readthrough! Reworking...
1.1 (Soups:Sprouts, Mung Bean) failed the readthrough! Reworking...
3.1 (Appetizers:Loquat) had incorrect measurements! Reworking...
1.1 (Soups:Sprouts, Mung Bean) had incorrect measurements! Reworking...
7.1 (Breakfast:Daikon Radish) tasted bad!  Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) tasted bad!  Reworking...
3.1 (Appetizers:Loquat) failed final editing!  Reworking...
7.1 (Breakfast:Daikon Radish) is ready for release!
3.1 (Appetizers:Loquat) tasted bad!  Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) tasted bad!  Reworking...
1.1 (Soups:Sprouts, Mung Bean) is ready for release!
3.1 (Appetizers:Loquat) is ready for release!
1.2 (Soups:Potato Bread) had incorrect measurements! Reworking...
1.2 (Soups:Potato Bread) is ready for release!
1.3 (Soups:Chicken Liver) failed the readthrough! Reworking...
3.2 (Appetizers:Bergenost) is ready for release!
1.3 (Soups:Chicken Liver) had incorrect measurements! Reworking...
7.2 (Breakfast:Lettuce, Red Leaf) failed final editing!  Reworking...
5.1 (Desserts:Mushroom, White, Silver Dollar) is ready for release!
5.2 (Desserts:Eggplant) is ready for release!
7.2 (Breakfast:Lettuce, Red Leaf) tasted bad!  Reworking...
3.3 (Appetizers:Tomato Red Roma) is ready for release!
1.3 (Soups:Chicken Liver) is ready for release!
3.4 (Appetizers:Guava) is ready for release!
5.3 (Desserts:Asparagus Peas) is ready for release!
1.4 (Soups:Cherimoya) is ready for release!
5.4 (Desserts:Squash, Kabocha) is ready for release!
1.5 (Soups:High-Protein Bread) had incorrect measurements! Reworking...
7.2 (Breakfast:Lettuce, Red Leaf) failed final editing!  Reworking...
1.5 (Soups:High-Protein Bread) failed final editing!  Reworking...
5.5 (Desserts:Sprouts, Radish) is ready for release!
3.5 (Appetizers:Squash Flower) is ready for release!
3.6 (Appetizers:Radishes, Red) failed the readthrough! Reworking...
1.5 (Soups:High-Protein Bread) is ready for release!
5.6 (Desserts:Mushroom, Black Trumpet) tasted bad!  Reworking...
1.6 (Soups:Flat Bread) is ready for release!
1.7 (Soups:Pomegranate) is ready for release!
3.6 (Appetizers:Radishes, Red) is ready for release!
7.2 (Breakfast:Lettuce, Red Leaf) is ready for release!
5.6 (Desserts:Mushroom, Black Trumpet) failed final editing!  Reworking...
1.8 (Soups:Carissa, Natal Plum) is ready for release!
7.3 (Breakfast:Alfalfa Sprouts) is ready for release!
7.4 (Breakfast:Tea Cakes) is ready for release!
5.6 (Desserts:Mushroom, Black Trumpet) is ready for release!
3.7 (Appetizers:Goose Liver) is ready for release!
1.9 (Soups:Ideal Flat Bread) is ready for release!
5.7 (Desserts:Tea Cakes) tasted bad!  Reworking...
3.8 (Appetizers:Okra) is ready for release!
3.9 (Appetizers:Borage) tasted bad!  Reworking...
3.9 (Appetizers:Borage) failed the readthrough! Reworking...
3.9 (Appetizers:Borage) failed the readthrough! Reworking...
7.5 (Breakfast:Chia seed) is ready for release!
3.9 (Appetizers:Borage) is ready for release!
1.10 (Soups:Banana Bread) is ready for release!
Finished Evaluating Chapter 1 - Soups
Evaluating Chapter 2 - Salads
3.10 (Appetizers:Peppers) is ready for release!
Finished Evaluating Chapter 3 - Appetizers
Evaluating Chapter 4 - Entrees
5.7 (Desserts:Tea Cakes) is ready for release!
7.6 (Breakfast:Tangerine) is ready for release!
4.1 (Entrees:Plantain) is ready for release!
4.2 (Entrees:Pignola (Pine)) failed the readthrough! Reworking...
2.1 (Salads:Caraway) is ready for release!
5.8 (Desserts:Blueberries) is ready for release!
5.9 (Desserts:Sago Palm) failed the readthrough! Reworking...
5.9 (Desserts:Sago Palm) tasted bad!  Reworking...
5.9 (Desserts:Sago Palm) is ready for release!
4.2 (Entrees:Pignola (Pine)) is ready for release!
2.2 (Salads:Potatoes, Red) is ready for release!
2.3 (Salads:Lemon) had incorrect measurements! Reworking...
4.3 (Entrees:Potatoes, Gold) is ready for release!
7.7 (Breakfast:Spinach) failed final editing!  Reworking...
2.3 (Salads:Lemon) had incorrect measurements! Reworking...
4.4 (Entrees:Ribeye) had incorrect measurements! Reworking...
7.7 (Breakfast:Spinach) tasted bad!  Reworking...
4.4 (Entrees:Ribeye) is ready for release!
2.3 (Salads:Lemon) tasted bad!  Reworking...
5.10 (Desserts:Opossum) is ready for release!
Finished Evaluating Chapter 5 - Desserts
Evaluating Chapter 6 - Snacks
6.1 (Snacks:Cheddar) is ready for release!
4.5 (Entrees:Sprouts, Mung Bean) is ready for release!
7.7 (Breakfast:Spinach) is ready for release!
6.2 (Snacks:Melon, Bitter) is ready for release!
6.3 (Snacks:Scallion) failed the readthrough! Reworking...
7.8 (Breakfast:Flank Steak) tasted bad!  Reworking...
2.3 (Salads:Lemon) failed final editing!  Reworking...
7.8 (Breakfast:Flank Steak) is ready for release!
4.6 (Entrees:Squash) is ready for release!
2.3 (Salads:Lemon) tasted bad!  Reworking...
4.7 (Entrees:Squash, Winter) failed the readthrough! Reworking...
4.7 (Entrees:Squash, Winter) had incorrect measurements! Reworking...
6.3 (Snacks:Scallion) is ready for release!
6.4 (Snacks:Squash Chayote) is ready for release!
4.7 (Entrees:Squash, Winter) is ready for release!
7.9 (Breakfast:Loganberries) is ready for release!
2.3 (Salads:Lemon) is ready for release!
7.10 (Breakfast:Opossum) is ready for release!
Finished Evaluating Chapter 7 - Breakfast
Evaluating Chapter 8 - Sandwiches
8.1 (Sandwiches:Rhubarb) had incorrect measurements! Reworking...
4.8 (Entrees:Corn, Blue) is ready for release!
2.4 (Salads:Cream cheese) failed final editing!  Reworking...
2.4 (Salads:Cream cheese) is ready for release!
6.5 (Snacks:Roasted Turkey) failed final editing!  Reworking...
4.9 (Entrees:Snake) is ready for release!
4.10 (Entrees:Prosciutto) failed the readthrough! Reworking...
6.5 (Snacks:Roasted Turkey) had incorrect measurements! Reworking...
2.5 (Salads:Artichokes, Domestic) tasted bad!  Reworking...
4.10 (Entrees:Prosciutto) tasted bad!  Reworking...
8.1 (Sandwiches:Rhubarb) tasted bad!  Reworking...
4.10 (Entrees:Prosciutto) had incorrect measurements! Reworking...
4.10 (Entrees:Prosciutto) is ready for release!
Finished Evaluating Chapter 4 - Entrees
6.5 (Snacks:Roasted Turkey) is ready for release!
6.6 (Snacks:Lime) had incorrect measurements! Reworking...
2.5 (Salads:Artichokes, Domestic) failed final editing!  Reworking...
8.1 (Sandwiches:Rhubarb) is ready for release!
6.6 (Snacks:Lime) tasted bad!  Reworking...
6.6 (Snacks:Lime) is ready for release!
2.5 (Salads:Artichokes, Domestic) is ready for release!
6.7 (Snacks:Hazelnut) is ready for release!
8.2 (Sandwiches:Pickle, Brine) is ready for release!
2.6 (Salads:Grapefruit) is ready for release!
2.7 (Salads:Lettuce, Iceberg) failed final editing!  Reworking...
2.7 (Salads:Lettuce, Iceberg) is ready for release!
6.8 (Snacks:Radishes, Daikon) is ready for release!
8.3 (Sandwiches:Oranges) is ready for release!
6.9 (Snacks:Salami) tasted bad!  Reworking...
2.8 (Salads:Fenugreek) is ready for release!
8.4 (Sandwiches:Chayote, Pipinella, Vegetable Pear) tasted bad!  Reworking...
2.9 (Salads:Ostrich) failed the readthrough! Reworking...
6.9 (Snacks:Salami) is ready for release!
6.10 (Snacks:Mushroom, Oyster) is ready for release!
Finished Evaluating Chapter 6 - Snacks
2.9 (Salads:Ostrich) failed final editing!  Reworking...
2.9 (Salads:Ostrich) failed the readthrough! Reworking...
2.9 (Salads:Ostrich) failed the readthrough! Reworking...
8.4 (Sandwiches:Chayote, Pipinella, Vegetable Pear) is ready for release!
8.5 (Sandwiches:Bear) is ready for release!
2.9 (Salads:Ostrich) failed final editing!  Reworking...
8.6 (Sandwiches:Panela) tasted bad!  Reworking...
8.6 (Sandwiches:Panela) failed the readthrough! Reworking...
2.9 (Salads:Ostrich) is ready for release!
8.6 (Sandwiches:Panela) had incorrect measurements! Reworking...
8.6 (Sandwiches:Panela) is ready for release!
2.10 (Salads:Brazil Nuts) is ready for release!
Finished Evaluating Chapter 2 - Salads
8.7 (Sandwiches:Peppers, Red) tasted bad!  Reworking...
8.7 (Sandwiches:Peppers, Red) tasted bad!  Reworking...
8.7 (Sandwiches:Peppers, Red) is ready for release!
8.8 (Sandwiches:Oat Bread) is ready for release!
8.9 (Sandwiches:Peppers, Green) is ready for release!
8.10 (Sandwiches:Garlic) is ready for release!
Finished Evaluating Chapter 8 - Sandwiches
***********************************************
Full Chapter Evaluation with PLINQ took: 00:00:25.1708103
***********************************************
Cookbook Evaluation Complete

If you are running a PLINQ query and the operation invoked throws an exception, it will not stop the set evaluation at that point but will continue to the end, recording any exceptions into an AggregateException, which can be caught after the query is evaluated (not declared).

See Also

The “Parallel LINQ” topic in the MSDN documentation.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required