LINQ to Objects

LINQ to Objects is used for accessing in-memory objects. LINQ to Objects is essentially the default LINQ implementation. It requires only the System.Core.Dll library, which is implicitly referenced in .NET Framework 3.5. Other providers, such as LINQ to SQL, require an additional interface and library. The source type for LINQ to Objects should implement the IEnumerable<T> interface. In addition, LINQ to Objects works with anything that can be described via an iterator. This provides considerable flexibility as to the range of data that is compatible with LINQ to Objects. The Factorial example presented earlier in this chapter is a perfect example. There are several advantages to using LINQ to Objects:

  • You can access in-memory objects declaratively instead of imperatively.

  • Declarative expressions typically are more concise than imperative code.

  • Complicated queries are easier to design and are more transparent.

  • Lazy evaluation generally is more efficient.

  • It is easy to access information in a different data domain.

Accessing memory objects declaratively is more straightforward than doing so imperatively. The imperative approach has been the dominant access model for in-memory objects. Much of the complexity of imperative programming is hidden in the LINQ interface. As such, declarative expressions typically are shorter than code that provides similar results. This is particularly useful with complex queries. For example, sequences are much more complicated in imperative code. Maintaining parallel sequences is challenging to write and maintain in imperative code. Lazy evaluation has been discussed previously and is supported in LINQ to Objects through the iterator model. Most developers are familiar with some query language for accessing databases. Adopting a similar model makes the transition from query languages to the LINQ to Objects model easier.

IEnumerable<T> is the pivotal interface for LINQ to Objects. If the source type supports the non-generic IEnumerable interface, you need to cast the source to IEnumerable<T>. As shown previously, there are two options: the Cast method or the OfType operator. This was demonstrated previously using the extension method syntax for query expressions and is shown again here:

var result =
    new Factorial().Cast<int>()
    .Select(f => f);

The language syntax is even simpler. In the following code, you set the element type explicitly in the query expression:

var result =
    from int f in new Factorial()
    select f;

Interestingly, when disassembled, these two pieces of code are exactly the same. Here is the disassembled code for the query expression using C# language syntax, which does not explicitly use the Cast method. The compiler calls the same Cast operator, the only difference being that the compiler has done the work, which is preferable:

IL_0006:  call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>
    [System.Core]System.Linq.Enumerable::Cast<int32>(class
        [mscorlib]System.Collections.IEnumerable)

Examples of LINQ to Objects

The following examples highlight the benefits of using LINQ to Objects. Each example highlights a different benefit of LINQ to Objects that was mentioned in the previous sections. Examples related to deferred execution (that is, lazy evaluation) were provided earlier in the chapter in the section "Core Elements."

Declarative versus Imperative

The following code iterates a string. A string is a collection of characters. In a foreach loop, the imperative code extracts uppercase characters from the string. Each uppercase character is then added to the result string:

string result = "";
string text = "Now Is The Time For All...";
foreach (char character in text) {
    if ((character > 64) && (character < 91)) {
        result += character;
    }
}

The following code accomplishes the same general task using LINQ to Objects (although this code returns a collection of characters versus a string). It is declarative, has fewer lines of code, and is more transparent:

var result = from character in "Now Is The Time For All..."
    where (character > 64) && (character < 91)
    select character;

LINQ to Objects Reduces Complexity

The following code iterates a list of stores and an inventory list. Items that are on sale are listed. The store type contains an inventory list. This code contains nested foreach loops and an if statement. This code is relatively complex, especially compared to code shown previously in this chapter:

foreach (Store store in stores) {
    foreach (Item i in store.inventory) {
        if (i.IsDiscounted) {
            Console.WriteLine("{0} {1}", store.Name, i.Name);
        }
    }
}

The following code performs the same task. However, its intention is clearer. LINQ to Objects is more self-documenting, and self-documenting code is easier to maintain and support. Another benefit to this code is that it is object-driven. The query expression returns an array of objects that combines data from the Store and Item classes. The preceding code is data-driven, which requires developer discipline for robustness. This query has two data sources, For that reason, there are two from statements—one for each data source:

var results = from store in stores
    from item in store.inventory
    where item.IsDiscounted == true
    select new { StoreName = store.Name, ItemName = item.Name};
foreach (var result in results) {
    Console.WriteLine(result.StoreName + " " + result.ItemName);
}

LINQ to Objects and Cross-Domain Access

One of the best features of LINQ is that it has a simple, straightforward method for cross-domain access using query expressions. This is particularly useful because it does not require converting the underlying types. A query expression can have several sources, where the sources can be from disparate data domains. You then can join the results from these different domains to create a cohesive result. For example, you can access a dataset and an XML data store simultaneously. The results can be combined in an anonymous class that consists of fields from both sources.

The following code has two sources: an in-memory collection and an XML store. The in-memory collection is mystore.inventory and contains a list of inventory items. The XML store is products, which contains a description of each inventory item. The query creates a collection of anonymous types that combine the products in inventory with the description of the product extracted from the XML store. The join operator used in the query expression is described in the section "LINQ Operators," later in this chapter:

var inventory = from item in mystore.inventory
    join product in products.Elements().Descendants("id")
    on item.ToString() equals product.Value.Trim(remove)
    select new { item, product.Parent };

This is the full listing of the program. At the start, a new store is created and items are added to the inventory. Then the XElement.Load function loads an XML file into memory, where XElement.Elements returns the collections of elements in the XML store. The remove array is used with the String.Trim method to remove extraneous characters from the value of XML elements:

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

namespace Donis.CSharpBook {
    class Program {
        static void Main(string[] args) {
            Store mystore = new Store();
            mystore.inventory.Add("101");
            mystore.inventory.Add("102");
            mystore.inventory.Add("101");
            mystore.inventory.Add("103");
            mystore.inventory.Add("101");

            char[] remove = { '	', '
', ' ' };

            XElement products = XElement.Load("inventory.xml");
            var inventory = from item in mystore.inventory
                            join product in products.Elements().Descendants("id")
                            on item.ToString() equals product.Value.Trim(remove)
                            select new { item, product.Parent };

            foreach (var stock in inventory) {
                Console.WriteLine("*******");
                Console.WriteLine(stock.item);
                Console.WriteLine(
                    stock.Parent.Element("name").Value.Trim(remove));
                Console.WriteLine(
                    stock.Parent.Element("price").Value.Trim(remove));
            }
        }
    }

    class Store {
        public string Name { get; set; }
        public List<string> inventory =
            new List<string>();
    }
}
..................Content has been hidden....................

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