In This Chapter
Exploring Select
Projecting New Types from Multiple Sources
Creating a New Sequence from Multiple Sequences with SelectMany
Using SelectMany
with Indexes
“Be kind, O Bacchus, take this empty pot offered to thee by Xenophon, the sot, Who, giving this, gives all that he has got.”
––Eratosthenes
We have used select
in many of the preceding chapters. This chapter focuses attention on select
through the conduit of many examples. This chapter provides several samples that demonstrate select
using things such as external application programming interfaces (APIs), InteropServices
, and a discussion of SelectMany
. SelectMany
demonstrates how to get data from multiple sequences.
Of critical importance in this chapter is a demonstration of a constructive way to use select
in the ubiquitous business layer that many n-tier applications contain.
Select
is both an extension method and a LINQ keyword. Programmers often use select
in LINQ queries, but sometimes they want to know the index of items in a sequence. This section has several code examples that demonstrate both variations of select
, including an example in Listing 10.4 that shows how to use the extension method form to add the indexing capability of the select
function.
Having been an aspiring mathematician—but a poor scholar—I am familiar with a couple of interesting algorithms that can be used to calculate prime numbers and other related math algorithms. Two of them are the sieve of Eratosthenes for calculating prime numbers and the Euclidean algorithm for determining greatest common divisors (GCD). Listing 10.1 demonstrates both algorithms. The code demonstrates a brute force Prime calculator that indicates a number is prime if it has no prime factors—the GCD algorithm—and a slightly faster one that uses the “Sieve” algorithm. (See Figure 10.1 for the output from code.)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SelectDemo1
{
class Program
{
static void Main(string[] args)
{
DateTime start = DateTime.Now;
const long upper = 1000000;
var numbers = new long[upper];
for (long i = 2000; i < 5000; i++)
numbers[i] = i+1;
/* prohibitively slow */
//var primes = from n in numbers
// where IsPrime(n)
// select n;
// use Sieve of Eratosthenes; of course now we have primes
BuildPrimes(upper);
var primes = from n in numbers
where IsPrime2(n)
select n;
DateTime stop = DateTime.Now;
StringBuilder builder = new StringBuilder();
Array.ForEach(primes.ToArray(), n=>builder.AppendFormat(“{0}
”, n));
Console.Write(builder.ToString());
Console.WriteLine(“Elapsed: {0}”, Elapsed(start, stop));
Console.ReadLine();
}
/// <summary>
/// Brute force prime tester, very slow.
/// </summary>
/// <param name=”v”></param>
/// <returns></returns>
private static bool IsPrime(long v)
{
if (v <= 1) return false;
for (long i = 1; i < v; i++)
if (Gcd(i, v) > 1)
return false;
return true;
}
/// <summary>
/// Use the Sieve of Eratosthenes: no number is divisible
/// by a number greater than its square root
/// </summary>
/// <param name=”v”></param>
/// <returns></returns>
private static bool IsPrime2(long v)
{
for(int i=0; i<Primes.Count; i++)
{
if(v % Primes[i] == 0) return false;
if(Primes[i] >= Math.Sqrt(v)) return true;
}
return true;
}
private static List<long> Primes = new List<long>();
private static void BuildPrimes(long max)
{
Primes.Add(2);
if (max < 3) return;
for (long i = 2; i <= max; i++)
{
if (IsPrime2(i))
Primes.Add(i);
}
}
/// <summary>
/// Recursive Euclidean algorithm
/// </summary>
/// <param name=”num”></param>
/// <param name=”den”></param>
/// <returns></returns>
private static long Gcd(long num, long den)
{
return den % num == 1 ? 1 :
den % num == 0 ? num : Gcd(den % num, num);
}
private static string Elapsed(DateTime start, DateTime stop)
{
TimeSpan span = stop - start;
return string.Format(“Days: {0}, Hours: {1}”,
“Minutes: {2}, Seconds: {3}, Mils: {4}”,
span.Days, span.Hours, span.Minutes, span.Seconds, span.Milliseconds);
}
}
}
In Listing 10.1, IsPrime
or IsPrime2
is called in the where
clause returning only prime numbers in the sequence. IsPrime
uses Euclid’s algorithm, recursively testing all of the numbers from 1 to n as possible divisors. This version is obviously quite slow as n gets to be very large. (You could speed up IsPrime
by only testing 1 to the square root of n, which is from Eratosthenes. This revision to IsPrime
is left as an example.) The second version IsPrime2
requires that you build all of the primes by testing a stored list of primes, storing the Primes in a List<T>
as you check candidates, and you only test the primes up to those that are less than or equal to the square root of the candidate number. The List<int>
of primes is seeded with the first Prime number 2.
As a useful general utility, the function Elapsed
can be used to see how long it takes for a function to complete. If you run IsPrime
up to a relatively small number like 1,000,000, the GCD approach is very slow. Using Eratosthenes’ algorithm to calculate primes, the code runs much faster but still grinds to a halt after about 10,000,000 or so. In practice, cryptographers are looking for huge prime numbers that even supercomputers cannot crack using any brute force algorithm. That’s why very large primes are useful in public key encryption; not because they can’t be determined but because they can’t be determined in any reasonable time frame.
The select
clause can be determined by calling functions. These predicates can also be manipulated directly. For example, if the from
clause uses num
in numbers, then select
is not limited to using num
as its operand. You can manipulate num
in the select
clause. Listing 10.2 uses a sequence of digits and multiplies each by 2 to yield a sequence of even numbers.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SelectDemo2
{
class Program
{
static void Main(string[] args)
{
var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var toEvens = from num in numbers
select num * 2;
Array.ForEach(toEvens.ToArray(), n => Console.WriteLine(n));
Console.ReadLine();
}
}
}
Although manipulating simple numbers is useful, a more common problem might be how to use LINQ in the style of programming employed for business applications today. That is, how do you use LINQ in what is commonly referred to as a business layer? The answer is that you define the query to construct a known type instead of an anonymous type, and you define the functions in the business tier to return an IEnumerable<T>
object where T is the business entity.
Listing 10.3 is a bit longer than the first two listings in this chapter, but this example need something that is practical as a business entity. In Listing 10.3, a Supplier
class is defined using automatic properties and an overloaded ToString
method for dumping entity state. An extension method is defined in ExtendsReader
to extend IDataReader
. This approach permits treating Supplier
as self-initializing but you wouldn’t necessarily have to convolute the entity class with ActiveX Data Objects (ADO) knowledge. ReadSupplier
acts like a member of IDataReader
and a generic delegate Func
is used to supply all of that tedious null checking.
Finally, a DataAccess
class provides a general ReadSuppliers
method that reads all suppliers (from Northwind) and a second ReadSuppliers
that accepts a generic Func
delegate to filter suppliers. The parameterless ReadSuppliers
returns an IEnumerable<Supplier>
and is pretty plain vanilla ADO.NET code. ReadSuppliers
with the predicate uses the first parameterless ReadSuppliers
and then uses a LINQ query with the Where
extension method and the predicate Func<Supplier, bool>
to filter the resultset. (Recall that Chapter 5, “Understanding Lambda Expressions and Closures,” introduced generic delegates.) In the example, the consumer provides the predicate. The predicate to ReadSuppliers—s => s.Country ==
“USA ”—is provided in the main function of Listing 10.3.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
namespace SelectDemo3
{
class Program
{
static void Main(string[] args)
{
IEnumerable<Supplier> USSuppliers =
DataAccess.ReadSuppliers(s => s.Country == “USA”);
Array.ForEach(USSuppliers.ToArray(), s => Console.WriteLine(s));
Console.ReadLine();
}
}
public class Supplier
{
public int SupplierID{ get; set; }
public string CompanyName{ get; set; }
public string ContactName{ get; set; }
public string ContactTitle{ get; set; }
public string Address{ get; set; }
public string City{ get; set; }
public string Region{ get; set; }
public string PostalCode{ get; set; }
public string Country{ get; set; }
public string Phone{ get; set; }
public string Fax{ get; set; }
public string HomePage { get; set; }
public override string ToString()
{
StringBuilder builder = new StringBuilder();
PropertyInfo[] props = this.GetType().GetProperties();
// using array for each
Array.ForEach(props.ToArray(), prop =>
builder.AppendFormat(“{0} : {1}”, prop.Name,
prop.GetValue(this, null) == null ? “<empty>
” :
prop.GetValue(this, null).ToString() + “
”));
return builder.ToString();
}
}
public static class DataAccess
{
private static readonly string connectionString =
“Data Source=.\SQLEXPRESS;”+
“AttachDbFilename=”C:\Books\Sams\”+
“LINQ\Northwind\northwnd.mdf”;Integrated Security=True;”+
“Connect Timeout=30;User Instance=True”;
public static IEnumerable<Supplier>
ReadSuppliers(Func<Supplier, bool> predicate)
{
IEnumerable<Supplier> suppliers = ReadSuppliers();
return (from s in suppliers select s).Where(predicate);
}
//public static List<Supplier> ReadSupplier(Func)
public static IEnumerable<Supplier> ReadSuppliers()
{
using(SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(“SELECT * FROM SUPPLIERS”,
connection);
IDataReader reader = command.ExecuteReader();
List<Supplier> list = new List<Supplier>();
while(reader.Read())
{
list.Add(reader.ReadSupplier());
}
return list;
}
}
}
public static class ExtendsReader
{
static Func<string, IDataReader, string, string> SafeRead =
(fieldName, reader, defaultValue) =>
reader[fieldName] != null ?
(string)Convert.ChangeType(reader[fieldName], typeof(string)) :
defaultValue;
public static Supplier ReadSupplier (this IDataReader reader)
{
Supplier Supplier = new Supplier();
supplier.supplierID = reader.GetInt32(0);
supplier.CompanyName = SafeRead(“CompanyName”, reader, ““);
supplier.ContactName = SafeRead(“ContactName”, reader, ““);
supplier.ContactTitle = SafeRead(“ContactTitle”, reader, ““);
supplier.Address = SafeRead(“Address”, reader, ““);
supplier.City = SafeRead(“City”, reader, ““);
supplier.Region = SafeRead(“Region”, reader, ““);
supplier.PostalCode = SafeRead(“PostalCode”, reader, ““);
supplier.Country = SafeRead(“Country”, reader, ““);
supplier.Phone = SafeRead(“Phone”, reader, ““);
supplier.Fax = SafeRead(“Fax”, reader, ““);
supplier.HomePage = SafeRead(“HomePage”, reader, ““);
return supplier;
}
}
}
Remember to adjust the connection string for your copy of the Northwind sample database. The results of the query are shown in Figure 10.2.
The Select
extension method has an implicit index variable. By using the Select
extension method and expressing an input argument for the Lambda Expression, you can define a projection that includes the ordinal position of each element in a sequence. Listing 10.4 demonstrates the technique.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SortingWithSelectIndex
{
class Program
{
static void Main(string[] args)
{
int[] cards = new int[52];
Random rand = new Random();
var shuffler = cards.Select((num, index) =>
new { Key = index, Random = rand.Next() });
Array.ForEach(shuffler.OrderBy(s=>s.Random).ToArray(),
s => Console.WriteLine(s));
Console.ReadLine();
}
}
}
The only drawback here is that you can’t use the index in the LINQ version of the code. For example
var shuffler = from card in cards select new {Key = index, Random = rand.Next()};
is not a valid LINQ query. However, you can simulate the index using the LINQ form of the call by defining an integer before the query and incrementing the integer each time the select
clause executes. This code has the same effect as the code in Listing 10.4:
int index = 0;
var result = from n in nums
select new
{
value = n,
key = index++
};
You can expand on this idea of randomizing, unsorting, or shuffling an array to form the basis of a card game, an element of which is the chance of the draw.
A couple of years ago, during a Blackjack craze, I wrote a Blackjack for Windows game. The game was designed to hint at statistically correct plays for all combinations, for example, what to do with a pair of 3s against a dealer 6. (It has helped my Blackjack game.) A short while after that, a woman from Harrah’s casino in Biloxi asked permission to use it as a pillow favor—sort of a chocolate on the pillow—at the casino. The game was fun to write, and I was happy to share.
The game (refer to Figure 10.3) uses the cards.dll
that is installed with Windows Solitaire and supports standard Blackjack play, including splitting, doubling-down, surrendering, holding, and hitting with hints that coach statistically perfect play.
Internally, the game combines Windows API calls to the cards.dll
, combining InteropServices
with GDI+. One small challenge was how to shuffle the cards. (There is no randomize array method in .NET, although for symmetry there should be an unsort behavior.) The solution: Define a structure with a random number and key. Increment the keys from 0 to 51 to represent the 52 cards, assign a random number to the random field, and by sorting the random field, scramble the cards. This actually takes a dozen lines of code or so.
The code in the rest of this section and its subsections demonstrates how to use LINQ to “shuffle” the cards, return a shuffled deck, and form the basis of a basic card game. The sample code in Listings 10.5, 10.6, and 10.7 contains code to import APIs from the cards.dll
, use GDI+ with unmanaged API methods, draw cards, and shuffle a digital representation of a deck of cards. Let’s start with the Main
function shown in Listing 10.5, which also contains all of the code together. (The subsections “Projecting New Types from Calculated Values,” “Importing DLLs,” and “Using GDI+ with the Windows API (or External DLL) Methods” elaborate on those subjects.)
A card is represented by a class of the same name. The class contains one each of the enumeration Face
and Suit
. There is also a Shuffler
struct that is used to represent the ordered cards; sorting the random field unorders, or shuffles, the cards. For example, shuffler.key
equal to 0 represents a card with the face of One
, an ace, and the Club
suit, 1 is the two of clubs, and so on.
In the Main
function, an array of the Shuffler
struct is created. The array contains 52 elements representing 52 cards. A simple loop is used to initialize the Shuffler.key
to the values 0 through 51 and Shuffler.random
to a random number. (It doesn’t matter if some of the random numbers are duplicated.) Next is a LINQ query ordering on Shuffler.random
and projecting a new instance of Card
for each element in the sequence. The actual Card
objects Suit
and Face
are derived with arithmetic. Suit
equals key divided by 13, the number of faces, and the Face
is derived by key modulo 13. Every fourteenth card is in the next Suit
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace SortingCards
{
class Program
{
public enum Face { One, Two, Three, Four, Five, Six, Seven,
Eight, Nine, Ten, Jack, Queen, King };
public enum Suit { Club, Diamond, Heart, Spade };
private static int width;
private static int height;
public struct Shuffler
{
public int key;
public int random;
}
public class Card
{
public Face Face { get; set; }
public Suit Suit { get; set; }
public override string ToString()
{
return string.Format(“{0} of {1}s”, Face, Suit);
}
}
static void Main(string[] args)
{
const int MAX = 52;
Random rand = new Random(DateTime.Now.Millisecond);
Shuffler[] Shuffler = new Shuffler[MAX];
for (int i = 0; i < MAX; i++)
{
shuffler[i].key = i;
shuffler[i].random = rand.Next();
}
// elem.key contains card number randomized by
// sorting on random numbers
var shuffledCards = from s in Shuffler
orderby s.random
select new Card { Suit = (Suit)(s.key / 13), Face = (Face)(s.key % 13) };
cdtInit(ref width, ref height);
try
{
using (Form form = new Form())
{
form.Show();
int i = 0;
foreach (var card in shuffledCards)
{
Graphics graphics = form.CreateGraphics();
graphics.Clear(form.BackColor);
string text = string.Format(“Index: {0}, Card: {1}”,
i++, card);
graphics.DrawString(text, form.Font, Brushes.Black, 10F,
(float)(height + 20));
PaintFace(graphics, card, 10, 10, width, height);
form.Update();
System.Threading.Thread.Sleep(500);
}
}
}
finally
{
cdtTerm();
}
Console.ReadLine();
}
private static void PaintFace(Graphics g, Card card,
int x, int y, int dx, int dy)
{
IntPtr hdc = g.GetHdc();
try
{
int intCard = (int)card.Face * 4 + (int)card.Suit;
cdtDrawExt(hdc, x, y, dx, dy, intCard, 0, 0);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
g.ReleaseHdc(hdc);
}
}
[DllImport(“cards.dll”)]
static extern bool cdtInit(ref int width, ref int height);
[DllImport(“cards.dll”)]
public static extern bool cdtDraw (IntPtr hdc, int x,
int y, int card, int type, long color);
[DllImport(“cards.dll”)]
public static extern bool cdtDrawExt(IntPtr hdc, int x, int y, int dx,
int dy, int card, int suit, long color);
[DllImport(“cards.dll”)]
public static extern void cdtTerm();
}
}
At the end of Main
, the cards library is initialized, a form is created, and each card is displayed for a half a second—Thread.Sleep(500)
—in its new position. When all of the cards have been displayed, the cards library cleanup happens—cdtTerm
is called. (Refer to Figure 10.4 for an example of the output to expect.)
The LINQ query that starts with var shuffledCards = from s in shuffler
demonstrates a projection to a specific, as opposed to anonymous, type. By using compound initialization with named parameters, you can construct Card
objects specifying the card Suit
and Face
.
In practice, modify the code to permit initialization with the value available, key
.
In key’
s setter in the Card
class, add the logic to convert the key to the Suit
and Face
. This is just good form. Using a factory to create cards is an appropriate alternative, too.
Library methods are imported with the DllImportAtttribute
and the name of the external DLL. They are also prefixed with static
and extern
keywords. For example, Listing 10.5 imports cdtInit
, cdtdraw
, cdtDrawExt
, and cdtTerm
from cards.dll
.
There are a couple of challenges when using external, nonmanaged API calls. One challenge is discerning correct usage. In this example, the cards.dll
was designed to expect a cleanup call to cdtTerm
. A second challenge is to get the declaration statement right.
When the types are straightforward as is the case in this example, the declarations are also straightforward. Things get muddied up when the API takes function pointers. Then, you need to use the MarshalAsAttribute
defined in System.Runtime.InteropServices
on the parameter.
Calls to external DLLs like cards.dll
can use raw Device Context (DC) handles in unmanaged ways. GDI+ uses a managed handle. The next section looks at how to convert a managed DC (from GDI+) to an unmanaged DC for GDI-based or older API calls.
GDI+ uses an uncached managed version of a device context, or DC. Device contexts are essentially graphics objects in GDI+. Older Windows API methods were designed to use a raw, unmanaged DC. Therefore, if you need to pass a device context to unmanaged library code, you need to do so safely. The method PaintFace
, excerpted from Listing 10.5, shows how you can request the DC from a graphics object, pass the DC to an external, old-style API method, and clean up the code in a finally
block with Graphics.ReleaseHdc
(shown separately in Listing 10.6).
private static void PaintFace(Graphics g, Card card, int x,
int y, int dx, int dy)
{
IntPtr hdc = g.GetHdc();
try
{
int intCard = (int)card.Face * 4 + (int)card.Suit;
cdtDrawExt(hdc, x, y, dx, dy, intCard, 0, 0);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
g.ReleaseHdc(hdc);
}
}
Writing books is a little like mind reading or forecasting. Part of the job is to figure out things the reader might want to do that they might not know how to do already. With many different levels of readers, different levels of code samples are devised. Some are easy and others are more challenging. The example demonstrates how you might convert the first letter of an array of words to an uppercase letter.
The literal string and subsequent splitting of that string is used here for convenience. The query starting with var icapped
demonstrates how to capitalize the first character of a string.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ICappingSelectDemo
{
class Program
{
static void Main(string[] args)
{
// array of strings for convenience
string wayfarism = “I’m still vertical”;
// create to array of strings
var values = wayfarism.Split(new char[] { ‘ ‘ },
StringSplitOptions.RemoveEmptyEntries);
// icap
var icapped = from s in values
select s.Replace(s[0], Convert.ToChar(s[0].ToString().ToUpper()));
icapped.WriteLineAll();
Console.ReadLine();
}
}
public static class Extender
{
public static void WriteLineAll<T>(this IEnumerable<T> ary)
{
Array.ForEach(ary.ToArray(), item=>Console.WriteLine(item));
}
}
}
Many other excellent resources are available on the web, including blogs, such as Bill Blogs in C# at http://srtsolutions.com/blogs/billwagner/ and ScottGu’s (Scott Guthrie) Blog at http://weblogs.asp.net/scottgu/, as well as excellent resources with general articles like my column VB Today at http://www.codeguru.com, InformIT.com, and 101 LINQ Samples by Microsoft at http://msdn2.microsoft.com/en-us/vcsharp/aa336746.aspx. You are encouraged to explore these resources and share their (free) availability with peers.
If you devise some clever queries, consider blogging about your own solutions or even submitting them to great sites like MSDN, InformIT, devsource.com, developer.com, or codeguru.com. Many of these sites compensate the author of original material.
When a LINQ query uses elements from one or more source types to define a new type, this is referred to as a projection. The compiler emits a class, part of whose name is AnonymousType. (Although you can use reflection to determine a projected type’s name and use that name in code, it isn’t a recommended practice.)
There are two key concepts for projecting new types: the Select
feature and the SelectMany
. Both Select
and SelectMany
are extension methods in the .NET Framework, but only select
is a LINQ keyword. The SelectMany
method comes into play when you write queries that have multiple from
clauses. Thus far, there have been many examples of select
; Listing 10.7 demonstrates an implicit SelectMany
. Listing 10.6 uses two classes: Customer
and Order
, and two collections containing instances of each. In the query that begins with var orderInfo
, the query is selecting from customers and orders and correlating the objects on customer.ID
and order.CustomerID
. The presence of the clause select new {Name = customer.CompanyName, Item=order.ItemDescription}
causes the compiler to emit a new class, an anonymous type referred to as a project.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace SelectManyDemo
{
class Program
{
public class Customer
{
public int ID{get; set;}
public string CompanyName{get; set; }
}
public class Order
{
public int ID{get; set; }
public int CustomerID{get; set; }
public string ItemDescription{get; set;}
}
static void Main(string[] args)
{
List<Customer> customers = new List<Customer>{
new Customer{ID=1, CompanyName=”Tom’s Toffees”},
new Customer{ID=2, CompanyName=”Karl’s Coffees”}};
List<Order> orders = new List<Order>{
new Order{ID=1, CustomerID=1, ItemDescription=”Granulated Sugar”},
new Order{ID=2, CustomerID=1, ItemDescription=”Molasses”},
new Order{ID=3, CustomerID=2, ItemDescription=”French Roast Beans”},
new Order{ID=4, CustomerID=2, ItemDescription=”Ceramic Cups”}};
var orderInfo = from customer in customers
from order in orders
where customer.ID == order.CustomerID
select new {Name=customer.CompanyName,
Item=order.ItemDescription};
Array.ForEach(orderInfo.ToArray(), o=>Console.WriteLine(
“Company: {0}, Item: {1}”, o.Name, o.Item));
Console.ReadLine();
}
}
}
The presence of more than one from
clause is technically taking two sequences and converting them into a single sequence. This is the equivalent of a SQL join. (Recall that in SQL, you can join with the literal join or by using where
predicates; the same is true for LINQ.)
The capability of LINQ to project new types—also called data shaping—is powerful. LINQ queries enable you to quickly and easily come up with brand-new types that previously hadn’t been defined in code, and mitigate the need to hand code classes for every possible combination of data you might need.
Phishing is what people are trying to do when they send out an email or phony link to try to get usernames and passwords from unsuspecting people. Excessive pop-ups, Trojans, worms, and viruses are all other kinds of despicable things that people do with computers. In some very small way, these programs challenge application and operating system writers to do a better job, so in some way people who write these obnoxious programs are useful (as much as a flesh-eating bacteria is useful).
One thing many of these harmful bits of code have in common is that they attack the Windows Registry. So, as a computer programmer and a computer user, it is helpful to understand how the Registry works. In addition, it is helpful to understand how the Registry works as a general programming aid—for when you need to add useful information to the Registry.
Tip
.NET introduced code access security (CAS). One of the things code access security does is permit assigning permissions to code. That is, CAS permissions can prohibit code from specific sources from doing things. For example, code access security can prohibit downloaded code from accessing the Registry.
The Windows Registry is a big dictionary (or database) of sorts containing hierarchical name and value pairs. .NET makes accessing the Registry for reads and writes pretty straightforward. With LINQ, you can actually write queries to scan and explore the Registry quite easily.
Consider the following scenario. Your program uses Windows event logging to write application exceptions to the event log. To work when installed, it needs to create an event source in the Registry at HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesEventlog
.
.NET provides the System.Diagnostics.EventLog
class and CreateEventSource
for this purpose. But, what if you want to validate that the key was created? You’d need to access the Registry and look for the key. A failure to create the key might be due to a lack of code access permissions, but you wouldn’t know unless a check was performed. To solve the problem, you could write a query that searched for the required key after the installer was to have created it. If the key is not found, then the installed application won’t work.
Another scenario might be to scan the Registry looking for differences. For example, you might want to look for keys on one part of the Registry that should exist in another part, or vice versa. The following code (see Listing 10.8) compares two sections of the Registry—LocalMachine
and CurrentUser
—and uses an implied SelectMany
with two from
and two where
clauses to find keys in common.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
namespace SelectingRegistryKeys
{
class Program
{
static void Main(string[] args)
{
var localMachineKeys = Registry.LocalMachine.OpenSubKey(“Software”).GetSub
KeyNames();
var userKeys = Registry.CurrentUser.OpenSubKey(“Software”).GetSubKeyNames();
// keys in common
var commonKeys = from machineKey in localMachineKeys
where machineKey.StartsWith(“A”)
from userKey in userKeys
where userKey.StartsWith(“A”) &&
/*where*/ machineKey == userKey
select machineKey;
Array.ForEach(commonKeys.ToArray(), key => Console.WriteLine(key));
Console.ReadLine();
}
}
}
Using set operations as described in Chapter 9, “Performing Set Operations,” you could use extension methods like Distinct
, Except
, or Interest
to find similarities or differences in the keys in the various Registry sections. (This is left as an exercise for the reader.)
SelectMany
is an extension method and is supported in LINQ with the keyword select
and multiple source sequences. Like the Select
extension method, the SelectMany
extension method supports the generic delegate Func
and indexes.
This chapter’s final example reuses some of the code from Listing 10.7 and an explicit SelectMany
to assign a random customer number to the customers. You might want to do this if you don’t want to reveal information useful to hackers, such as the actual primary key IDs in your database’s schema.
using System;
using System.Linq;
using System.Collections.Generic;
namespace SelectManyWithIndex
{
class Program
{
public class Customer
{
public int ID{get; set;}
public string CompanyName{get; set; }
}
public class Order
{
public int ID{get; set; }
public int CustomerID{get; set; }
public string ItemDescription{get; set;}
}
static void Main(string[] args)
{
List<Customer> customers = new List<Customer>{
new Customer{ID=1, CompanyName=”Tom’s Toffees”},
new Customer{ID=2, CompanyName=”Karl’s Coffees”}};
List<Order> orders = new List<Order>{
new Order{ID=1, CustomerID=1, ItemDescription=”Granulated Sugar”},
new Order{ID=2, CustomerID=1, ItemDescription=”Molasses”},
new Order{ID=3, CustomerID=2, ItemDescription=”French Roast Beans”},
new Order{ID=4, CustomerID=2, ItemDescription=”Ceramic Cups”}};
var orderInfo =
customers.SelectMany(
(customer, index) =>
from order in orders
where order.CustomerID == customer.ID
select new { Key = index + 1, Customer = customer.CompanyName,
Item = order.ItemDescription });
Array.ForEach(orderInfo.ToArray(), o=>Console.WriteLine(
“Key: {0}, Name: {1}, Item: {2}”, o.Key, o.Customer, o.Item));
Console.ReadLine();
}
}
}
The code in Listing 10.9 uses this overloaded version of SelectMany
:
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, int, IEnumerable<TResult>> selector
The static
qualifier and this
for the first parameter IEnumerable<TSource>
indicates that the method is an extension method for IEnumerable<T>
. The argument represented by this
is actually the instance argument customers
in the listing. The second parameter beginning with the generic delegate Func
indicates that the first generic parameter is the input customer and the second is an index. The Lambda Expression
(customer, index) => from order in orders where order.CustomerID
== customer.ID select new { Key = index + 1, Customer = customer.CompanyName,
Item = order.ItemDescription }
satisfies the generic delegate Func
and the expression body—the LINQ query—returns the result type IEnumerable<TResult>
of the generic delegate.
It is important not to get behind on advanced subjects like generics because languages build complexity in layers. The very cryptic looking SelectMany
overloaded method is a perfect (and somewhat intimidating) example of that layering of capabilities.
You can’t write a LINQ query without using select
. So, you have seen many select
statements in previous chapters, and you will see many more in the remaining chapters. But, the purpose of this book is to zoom our microscope in on a specific branch of the .NET Framework.
This chapter focused on exploring several examples using select
and select many. You now have most of the basis and underpinnings mastered, so this part wraps up with an exploration of joins and exploring external objects models. Parts III and IV look at how LINQ works with ADO.NET and Extensible Markup Language (XML).
18.118.2.240