C H A P T E R  7

Powerful C# Constructs

The .NET Framework has remained very vibrant since the introduction of .NET 1.0 in early 2002. Over the years, along with the major releases of .NET, Microsoft has added new languages, features, and technology sets. It is very clear that a lot of planning and consideration has gone into the .NET Framework. The same thoughtfulness is apparent in the evolution of the C# language. With each release, the C# language has added new and powerful constructs that have improved the language and the way developers write code in C#.

With C# 2.0, the language designers introduced new features, such as

  • Generic types and methods
  • Nullable types
  • Simplified delegates and anonymous methods
  • Iterators and the yield statement
  • Partial classes

With C# 3.0, the following new features were added:

  • Implicitly typed local variables
  • Anonymous types
  • Extension methods
  • Lambda expressions
  • Query syntax and LINQ

With C# 4.0, the language brought more features:

  • Optional parameters and named arguments
  • Dynamic binding and the Dynamic Language Runtime (DLR)
  • Covariance and contravariance

This chapter will review some of the powerful C# constructs, as they relate to following best practices. The most important concept is that effective C# development is a key part of following .NET practices. Take the time to dive into the C# language to learn how to take full advantage of the language. Table 7-1 lists the ruthlessly helpful practices that take advantage of some of the powerful C# constructs.

Images

Extension Methods

Extension methods are a special kind of static method. They allow you to write methods as if they were instance methods on an existing type. This approach does not involve creating a derived type, nor does it involve recompiling or modifying the original type. The extension method is called as if it is a method on the type it extends and appears (even through Visual Studio IntelliSense) as if it is actually defined as a method of the type.

Let’s take a look at the power of extension methods by way of an example. In the example system there is different logic that is driven by the current day of the week. There is a frequent need to know if today’s date is a weekday or a weekend. By using the DayOfWeek property of System.DateTime it is simple to properly branch the logic. Listing 7-1 shows a typical section of code with logic that repeats throughout the codebase.

Listing 7-1. Repeating Code That Acts on a DateTime

DateTime importantDate = new DateTime(2011, 5, 7);

switch (importantDate.DayOfWeek)
{
    case DayOfWeek.Saturday:
    case DayOfWeek.Sunday:
        WeekendProcessing();
        break;
    default:
        WeekdayProcessing();
        break;
}

For this example, the team wants to reduce the amount of repeating code. To accomplish this the team employs the Library pattern and writes a DateTimeHelper class.1 Notice the uses of the static keyword. This is a static class with a static method named IsWeekend, which is shown in Listing 7-2.

Listing 7-2. Library Pattern Introduces the IsWeekend H elper Method

public static class DateTimeHelper
{
    using System;

    public static bool IsWeekend(DateTime dateTime)
    {
        switch (dateTime.DayOfWeek)
        {
            case DayOfWeek.Saturday:
            case DayOfWeek.Sunday:
                return true;
            default:
                return false;
        }
    }
}

The helper method approach does achieve reuse. The code in Listing 7-3 performs exactly the same as the original code in Listing 7-1 in a lot fewer lines of code. The code in Listing 7-3 achieves all the desired results. It promotes reuse, and the resulting code is straightforward and readable, although the semantics of calling a method on a helper class is a bit awkward.

Listing 7-3. Helper Method Approach to Calling IsWeekend

DateTime importantDate = new DateTime(2011, 5, 7);

if (DateTimeHelper.IsWeekend(importantDate))
    WeekendProcessing();
else
    WeekdayProcessing();

This reuse is helpful, but it would be nice if System.DateTime actually had the IsWeekend method. That way any developer on the team wouldn’t need to know which methods are part of DateTime and which methods are in the helper class. By using an extension method, the IsWeekend method can effectively extend DateTime, appearing to be one of the DateTime methods.

Writing the IsWeekend method as an extension method is shown in Listing 7-4. The only real difference between the helper method in Listing 7-2 and the extension method in List 7-4 is that the method parameter starts with the this keyword. The static keyword is applied in the same way and the logic is identical.

__________

1 The Library pattern is described in Kent Beck, Implementation Patterns (Upper Saddle River, NJ: Addison-Wesley Professional, 2008).

Listing 7-4. Declaring the IsWeekend Extension Method

namespace Lender.Slos.Extensions
{
    using System;

    public static class DateTimeExtensions
    {
        public static bool IsWeekend(this DateTime dateTime)
        {
            switch (dateTime.DayOfWeek)
            {
                case DayOfWeek.Saturday:
                case DayOfWeek.Sunday:
                    return true;
                default:
                    return false;
            }
        }
    }
}

images Note The naming convention for a class that contains extension methods is usually {Type Name}Extensions. For the namespace name there’s no clear convention. A common practice is to create a separate project and put all extension methods under one namespace, such as, Lender.Slos.Extensions.

Extension methods must be defined as static methods in a static class and are identified by placing the this keyword in front of the first parameter in the method signature. The method extends the behavior of an object of the type of the first parameter. There are a few other restrictions, but they are rarely an issue.2

In Listing 7-5, the IsWeekend extension method appears, for all intents and purposes, to be a method of System.DateTime. There is no reference to the DateTimeExtensions class; the compiler knows how to find the extension method without needing to specify the class.

Listing 7-5. Extension Method Approach to Calling IsWeekend

DateTime importantDate = new DateTime(2011, 5, 7);

if (importantDate.IsWeekend())
    WeekendProcessing();
else
    WeekdayProcessing();

__________

2 Extension methods cannot extend a static class. Also, if there is already an existing method in a type with the same signature as an extension method then the extension method is never called.

In many ways this is remarkable. From the code listing and through Visual Studio IntelliSense, the IsWeekend method appears to be defined within System.DateTime, a .NET type that is defined in another assembly. The extension method approach has the following benefits:

  • Code reuse through a helper method
  • Straightforward and readable code
  • Visual Studio IntelliSense support
  • Code semantics are clear and convenient

To continue this example, other application-specific methods could be defined as extension methods, such as, IsHoliday or IsWorkingDay. These extension methods can be found in the sample code within the SamplesCh071_ExtensionMethodsLender.SlosDateTimeExtensions.cs file.

In general, extension methods are an effective way to add new behavior and functionality to a type that is closed to modification.

images Practice 7-1 Use Extension Methods to Add Behavior to Types That Are Closed to Modification

Implicitly Typed Local Variables

Should you use the var keyword to declare a local variable in C#? This question seems to come up a lot. Ever since implicitly typed local variables were introduced in C# there have been frequent discussions about whether and when it is a good idea to use implicit typing. As with so many things, there are uses for and misuse of implicitly typed local variables.3 This section focuses on the usefulness of declaring local variables using the var keyword.

Before discussing the usefulness of implicitly typed local variables, it is important to dispel any misconceptions. Some common fallacies are laid out in Table 7-2.

__________

Images

There are situations where the var keyword is required. For anonymous types to work, implicitly typed local variables are required. Since the type is anonymous, the developer cannot explicitly type the variable. The compiler handles all the implicit typing of anonymous types. In this code snippet, the compiler creates a strongly typed album variable based on the specification implicit in the anonymous type declaration. The compiler performs type checking of the album variable’s usage. IntelliSense lists the Artist and Title properties just like any other type.

var album = new { Artist = "U2", Title = "Rattle And Hum" };

Console.WriteLine(""{0}", by {1}", album.Title, album.Artist);

Beyond anonymous types and the essential need for the var keyword, implicit typing serves other useful purposes. An obvious reason is that by using the var keyword the code is made more readable by eliminating redundant type declarations. Compare the following two code fragments. Clearly, there is no value in writing out the explicit type when declaring the artists and albums local variables. The type of both artists and albums is self-evident in the Process2 method. There is no disadvantage to using the var keyword in this circumstance.

private void Process1()
{
    List<Artist> artists = new List<Artist>();
    Dictionary<Artist, Album> albums = new Dictionary<Artist, Album>();
...

private void Process2()
{
    var artists = new List<Artist>();
    var albums = new Dictionary<Artist, Album>();
...

Perhaps the best argument for the implicit typing of local variables relates to code refactoring. For example, the code in Listing 7-6 explicitly types the results variable as ApplicationCollection. However, the code only uses the results variable’s IEnumerable<Application> interface to loop through the collection. In other words, the code is tightly coupled to the ApplicationCollection class for no good reason.

Listing 7-6. Sample Code That Uses Explicit Typing

ApplicationRepository repository
    = new ApplicationRepository();

string lastNameCriteria = "P";

Controller controller = new Controller(repository);

ApplicationCollection results = controller.Search(lastNameCriteria);

foreach (var result in results)
{
    Console.WriteLine("{0}, {1}",
        result.LastName,
        result.FirstName);
}

This is the signature of the Search method that is called by the code in Listing 7-6. The call to the Search method is explicitly typed to ApplicationCollection, not because the code requires that type, but because it is the return type of the method.

public ApplicationCollection Search(string lastNameCriteria)

Listing 7-7 shows this example rewritten using implicitly typed local variables. The results variable is declared with the var keyword to loosely couple the variable from the Search method’s return type. As long as the results can be iterated over in the foreach loop then the code works as intended. More importantly, the compiler enforces the minimum essential requirements of the results variable.

Listing 7-7. Sample Code That Uses Implicit Typing

var repository = new ApplicationRepository();

var lastNameCriteria = "P";

var controller = new Controller(repository);

var results = controller.Search(lastNameCriteria);

foreach (var result in results)
{
    Console.WriteLine("{0}, {1}",
        result.LastName,
        result.FirstName);
}

At some future date, the signature of the Search method could change to return the IEnumerable<Application> type, as follows:

public IEnumerable<Application> Search(string lastNameCriteria)

This change has no impact on the code in Listing 7-7 because the compiler has implicitly typed the results variable. However, this change causes the code in Listing 7-6 to generate a compiler error something like the following:


Error Cannot implicitly convert type 'IEnumerable< Application>' to
'ApplicationCollection'.

There are advantages to using implicit typing whenever the compiler permits. This is especially true for unit test code where implicitly typed local variables help to keep the test code loosely coupled to the code under test. This point is made again in Chapter 8 when better practices for automated testing are presented. Using implicit typing whenever the compiler permits is a more aggressive stance that recognizes that the compiler knows what type the variable must be without you having to explicitly type it out. If it is not implicitly clear then there will be a compiler error.

images Practice 7-2 Use the var Keyword to Loosely Couple Local Variables from Their Implicit Type

In general, refactoring is made easier with implicit typing. Between compiler checking and automated testing, there is virtually no semantic reason not to implicitly type local variables. There are other reasons to explicitly type variables. These reasons are often based on opinion, a team preference, or better readability. Considering your specific situation

  • If it is important that someone reading the code knows the type of the variable at a glance, use explicit typing.
  • Experiment writing the code both ways and adopt the style you consider more readable.

Nullable Types

In the C# language, variables of reference types (classes, interfaces, and delegates) can be assigned the null value. Value types (enumerations, numeric types, Booleans, and user-defined structs) cannot be assigned the null value. Nullable types, introduced in .NET 2.0, are used to assign the null value to a variable that represents an underlying value type. These nullable types are instances of the System.Nullable<T> struct, where the type parameter T is a value type. More common is the syntax T?, which is a shorthand for System.Nullable<T>, where T is a value type.

Let’s take a look at an example of how nullable types are used. The code in Listing 7-8 defines the Application class. The Id property is defined with the value type of System.Guid. The logic in the Save method assumes that if the Id property is the default value of Guid.Empty then the proper action is to create the entity, otherwise the Save method updates the entity. This logic works; however, it can be made more explicit if the Id property is defined as a nullable type.

Listing 7-8. The Application Class Not Using a Nullable Id Property

public class Application
{
...

    public Guid Id { get; private set; }

...

    public void Save()
    {
        Validate();

        var entity = CreateEntity();

        if (Id == Guid.Empty)
        {
            entity.Id = _applicationRepo.Create(entity);
        }
        else
        {
            entity.Id = Id;
            _applicationRepo.Update(entity);
        }

        Syncronize(entity);
    }

...
}

In Listing 7-9, the Application class is rewritten with the Id property defined as a nullable Guid. Now, the logic in the Save method is written with clear semantics. If the Id property does not have a value then the entity is created. Otherwise, the entity’s Id property is assigned the Id value and the record is updated.

Listing 7-9. The Application Class with Id Defined As a Nullable Type

public class Application
{
...

    public Guid? Id { get; private set; }

...

    public void Save()
    {
        Validate();

        var entity = CreateEntity();
        if (!Id.HasValue)
        {
            entity.Id = _applicationRepo.Create(entity);
        }
        else
        {
            entity.Id = Id.Value;
            _applicationRepo.Update(entity);
        }

        Syncronize(entity);
    }

...
}

Any variables, parameters, or properties that are defined using a nullable type make it clear that null is a legitimate and anticipated value. Alternately, defining a variable, parameter, or property with a value type makes it clear that an explicit value assignment is expected. This helps make the method and class usage clearer to the consumer.

images Practice 7-3 Use Nullable Types to Designate Variables, Parameters, and Properties That Are Nullable.

The Null-Coalescing Operator

The null-coalescing operator (??) is used to define a default value for both nullable types and reference types. In the expression count ?? -1, the null-coalescing operator returns the left-hand operand (the value of count) if it is not null; otherwise it returns the right operand (-1).

Listing 7-10 shows examples of how the null-coalescing operator provides a succinct way to handle null values. For this example class, if the DateOfApplication property of the class is null then the DateOfApplication property of the returned entity is DateTime.Today. This logic is handled in one line, which is shown in bold in Listing 7-10, by using the null-coalescing operator. In this listing, there are other properties that are coalesced so as to return the proper default value. This includes the two strings properties, MiddleInitial and Suffix, and the three numeric values.

Listing 7-10. Handling null Value Assignment with the Null-Coalescing Operator

private ApplicationEntity CreateEntity()
{
    return new ApplicationEntity
        {
            LastName = LastName,
            FirstName = FirstName,
            MiddleInitial = MiddleInitial ?? string.Empty,
            Suffix = Suffix ?? string.Empty,
            DateOfBirth = DateOfBirth,
            DateOnApplication = DateOnApplication ?? DateTime.Today,
            Principal = Principal ?? MaximumLoanAmount,
            AnnualPercentageRate = AnnualPercentageRate ?? DefaultAnnualPercentageRate,
            TotalPayments = TotalPayments ?? DefaultTotalPayments,
        };
}

images Practice 7-4 Use the Null-Coalescing Operator (??) to Efficiently Apply Null-Value Assignment Logic

Optional Parameters

VB.NET has had optional parameters for quite a while. In C# 4.0, optional parameters were introduced as a new language feature. When you specify that a method parameter is optional, no argument has to be supplied for that parameter when the method is called. Optional parameters are indicated by assigning the parameter a value in the method definition. For instance, the middleInitial parameter of a method written as string middleInitial = null defines it as an optional parameter. The primary restriction is that all optional parameters must come after required parameters of a method4.

The code shown in Listing 7-11 defines a method that instantiates an Application class based on the parameter values provided. Only the first three parameters, lastName, firstName, and dateOfBirth, are considered required parameters.

Listing 7-11. The CreateApplication Method with All Parameters

public Application CreateApplication(
    string lastName,
    string firstName,
    DateTime dateOfBirth,
    string middleInitial,
    string suffix,
    DateTime dateOnApplication,
    decimal principal,
    decimal annualPercentageRate,
    int totalPayments)
{
    if (string.IsNullOrWhiteSpace(lastName))
    {
        throw new ArgumentNullException("lastName");
    }

    if (string.IsNullOrWhiteSpace(firstName))
    {
        throw new ArgumentNullException("firstName");
    }
    if (dateOfBirth < Application.MinimumDateOfBirth)
    {
        throw new ArgumentOutOfRangeException("dateOfBirth");
    }

    return new Application(null)
    {
        LastName = lastName,
        FirstName = firstName,
        MiddleInitial = middleInitial ?? string.Empty,
        Suffix = suffix ?? string.Empty,
        DateOfBirth = dateOfBirth,
        DateOnApplication = dateOnApplication,
        Principal = principal,
        AnnualPercentageRate = annualPercentageRate,
        TotalPayments = totalPayments,
    };
}

__________

Before optional parameters were introduced into C#, method overloading was the general way to provide method callers different calling signatures. The code in Listing 7-12 illustrates how the one method in Listing 7-11 is overloaded with a method that requires only the mandatory parameters.

Listing 7-12. Method Overloads of CreateApplication with Various Parameters

public Application CreateApplication(
    string lastName,
    string firstName,
    DateTime dateOfBirth)
{
    return CreateApplication(
        lastName,
        firstName,
        dateOfBirth,
        string.Empty,
        string.Empty,
        DateTime.Today,
        Application.MaximumLoanAmount,
        Application.DefaultAnnualPercentageRate,
        Application.DefaultTotalPayments);
}

The method overloading concept in Listing 7-12 can be further extended to many more methods that simply add in the optional parameters, such as the following:

public Application CreateApplication(
    string lastName,
    string firstName,
    DateTime dateOfBirth,
    string middleInitial)
{
    ...
}
public Application CreateApplication(
    string lastName,
    string firstName,
    DateTime dateOfBirth,
    string middleInitial,
    string suffix)
{
    ...
}

With .NET 4.0 optional parameters finally came to the C# language. Instead of writing a series of method overloads, it is now possible to define one method that has optional parameters. In Listing 7-13, the CreateApplication is rewritten using optional parameters. This one method replaces the method in Listing 7-11 and the six overloaded methods that might have been written.

Listing 7-13. The CreateApplication Method with Optional Parameters

public Application CreateApplication(
    string lastName,
    string firstName,
    DateTime dateOfBirth,
    string middleInitial = null,
    string suffix = null,
    DateTime? dateOnApplication = null,
    decimal? principal = null,
    decimal? annualPercentageRate = null,
    int? totalPayments = null)
{
    if (string.IsNullOrWhiteSpace(lastName))
    {
        throw new ArgumentNullException("lastName");
    }

    if (string.IsNullOrWhiteSpace(firstName))
    {
        throw new ArgumentNullException("firstName");
    }

    if (dateOfBirth < Application.MinimumDateOfBirth)
    {
        throw new ArgumentOutOfRangeException("dateOfBirth");
    }

    return new Application(null)
    {
        LastName = lastName,
        FirstName = firstName,
        MiddleInitial = middleInitial ?? string.Empty,
        Suffix = suffix ?? string.Empty,
        DateOfBirth = dateOfBirth,
        DateOnApplication = dateOnApplication ?? DateTime.Today,
        Principal = principal ?? Application.MaximumLoanAmount,
        AnnualPercentageRate =
            annualPercentageRate ?? Application.DefaultAnnualPercentageRate,
        TotalPayments = totalPayments ?? Application.DefaultTotalPayments,
    };
}

Instead of writing many method overloads to handle the various method signatures, one method with optional parameters can help make the code easier to maintain.

images Practice 7-5 Define a Method with Optional Parameters to Replace Overloaded Methods

There is an important cautionary note that relates to constant value assignment when declaring an optional parameter. It is probably better to describe the potential problem with an example. In the following code snippet, the optional loanAmount parameter is provided a constant value of 17500, if the parameter is not provided.

public class LoanValidator
{
    public static readonly int LoanLimit = 17500;

    public int LoanCeiling(int loanAmount = 17500)
    {
        return loanAmount;
    }
}

When the code calls the LoanCeiling method without the optional parameter, the compiler references the value and copies it directly into the calling code. The implications are that if you change the default value, such as 18000, all the code that calls the method must be recompiled to get the new value. The following output demonstrates the results from a test assembly that is not recompiled after the value changes from 17500 to 18000. The method call to the test assembly still uses the original value and the test fails.


Errors and Failures:
1) Test Failure :
Tests.Unit.Lender.Slos.OptionalParameters.OptionalParametersTests.LoanCeiling_WithNoParamete
r_ExpectProperValue
  Expected: 18000
  But was:  17500

The better practice is to use a nullable type when defining an optional parameter. In this way the null-coalescing operator is used to encapsulate how the code ought to properly handle the case when the parameter is not explicitly provided by the calling code. This is shown in the following code sample:

public class LoanValidator
{
    public static readonly int LoanLimit = 17500;
    public int LoanCeiling(int? loanAmount = null)
    {
        return loanAmount ?? LoanLimit;
    }
}

Generics

Since .NET 2.0, there are two forms of generics in the C# language:

  • Generic Types: A class, structure, interface, or delegate defined with one or more type parameter
  • Generic Methods: A method defined with one or more type parameter

Before .NET 2.0, the ArrayList class was in common use. This collection type can contain a list of any object type. Since every type is derived from the object type, the list could contain any type. There was no type safety and improper usages might not be found until runtime. For example, the following code sample has a simple mistake. The value in the names array is improperly cast into the dateOfBirth variable.

var names = new ArrayList();
var datesOfBirth = new ArrayList();

names.Add("Public, John Q.");
datesOfBirth.Add(new DateTime(1993, 11, 13));

for (int index = 0; index < names.Count; index++)
{
    DateTime dateOfBirth = (DateTime)names[index];

    Console.WriteLine("{0}, DOB: {1}",
        names[index],
        dateOfBirth.ToShortDateString());
}

This mistake is not found by the compiler. When the code is run the result is the following runtime error:


InvalidCastException: Specified cast is not valid.

In .NET 2.0 and beyond, the code can easily be rewritten to use generic lists. The explicit cast to DateTime is no longer necessary. Without the cast it is clear that the dateOfBirth variable should not be assigned from the names list.

var names = new List<string>();
var datesOfBirth = new List<DateTime>();

names.Add("Public, John Q.");
datesOfBirth.Add(new DateTime(1993, 11, 13));
for (int index = 0; index < names.Count; index++)
{
    DateTime dateOfBirth = names[index];

    Console.WriteLine("{0}, DOB: {1}",
        names[index],
        dateOfBirth.ToShortDateString());
}

The compiler finds the DateTime conversion error and the code will not compile. Even if dateOfBirth is defined as an implicitly typed local variable, this mistake is found by the compiler and the code will not compile, reporting this error:


'string' does not contain a definition for 'ToShortDateString' and no extension method
'ToShortDateString' accepting a first argument of type 'string' could be found (press F4 to
add a using directive or assembly reference)

images Practice 7-6 Use Generics to Help the Compiler Enforce Type Checking at Compile Time

Since .NET 2.0 introduced them, the generic collection types are now widely used. These generic classes have gone a long way to prevent improper usage, such as calling instance methods with improper arguments. The compiler catches the error if the Add method is called passing the wrong type. This same type safety and compiler enforcement of the generic types and generic methods written by you and your team is very useful.

images Practice 7-7 Develop Class Libraries That Use Generics for Type-Safety and to Enforce Proper Usage

LINQ

LINQ is an acronym for language-integrated query. It is a powerful .NET technology that brings querying capabilities directly into the C# language (and other .NET languages). With the introduction of LINQ in .NET 3.0, querying was brought to C# as a major language construct, as fundamental as foreach loops, delegates, and extension methods. It is hard to overstate the impact that LINQ has had on developing .NET applications that perform a lot of queries and the developers who write them.

LINQ is a large topic. There are many relevant language features vital to LINQ, including extension methods, lambda expressions, anonymous data types, and partial methods. There are many keywords and many query constructs. Entire books are devoted to LINQ.5 For this reason, this section discusses the importance of LINQ and why it is a powerful C# construct, and provides advice to encourage new and different practices.

__________

5 An excellent book on LINQ is Joseph Rattz and Adam Freeman, Pro LINQ, Language Integrated Query in C# 2010 (New York: Apress, 2010).

There are two basic ways to write LINQ queries:

  • Query Syntax: A declarative syntax introduced in C# 3.0, which is translated at compile time to method calls understood by the .NET common language runtime (CLR).
  • Method Syntax: The standard query methods understood by the .NET CLR, called directly instead of using the query syntax.

There is no semantic difference between query syntax and method syntax. Query syntax is often considered to be simpler and more readable. It is certainly analogous to the structured query language (SQL), which is the de facto standard way to query in database products like Microsoft SQL Server. The following C# statement (using the LINQ to SQL provider) queries a database in the LINQ query syntax:

var results = from a in Albums
      where a.Artist.Name.StartsWith("B")
      select a;

The statement reads like a reshuffled SQL query. In fact, it is equivalent to the following SQL statement:

SELECT [t0].*
FROM [Album] AS [t0]
    INNER JOIN [Artist] AS [t1]
    ON [t1].[ArtistId] = [t0].[ArtistId]
WHERE [t1].[Name] LIKE 'B%'

The power of LINQ comes from the way the query syntax is directly supported by the C# language. For example, the LINQ provider presents the inner join relationship between Artist and Albums as a property of the class. In addition, the Name property is strongly typed as a string. These relationships and types are checked and enforced by the compiler. With LINQ, querying is incorporated into the C# language in a fundamental and robust manner.

images Practice 7-8 Use LINQ to Perform Querying in a Way That Is Incorporated into the C# Language

The .NET common language runtime (CLR) does not directly use the query syntax. The compiler transparently translates the query syntax into the method syntax. The query syntax statement above is equivalent to the following statement written in the method syntax:

var results = Albums
    .Where(x => x.Artist.Name.StartsWith("B"));

Many C# developers who are more familiar with fluent interfaces and lambda syntax prefer writing LINQ expressions in method syntax. It seems logical that if the team prefers one convention over another then the team should stick to that convention. With a little practice, it should not be hard for developers to adapt from one convention to the other.

Even if you prefer the query syntax, it is important to learn the method syntax:

  • Some queries can only be expressed as method calls.
  • The MSDN reference documentation of the System.Linq namespace uses method syntax.

Even if you prefer the method syntax, it is important to learn the query syntax:

  • The query syntax supports a local range variable, which represents each element in the source sequence. This can make complex queries a lot more readable.
  • The query syntax supports the use of a let clause, which is useful to store the result of a sub-expression. The let clause helps make complex queries more readable.

Some developers seem to draw the battle lines with either LINQ query syntax or method syntax. A better approach is to see the need for both LINQ query syntax and method syntax. The project or the organization may have a preference and establish a convention, and that can be the final form written for the production code. However, you and your team will certainly benefit from gaining proficiency and fluency in both syntaxes, which will make your development more effective.

images Practice 7-9 Attain Proficiency in Both the LINQ Query Syntax and Method Syntax

For years, developers have been using the Microsoft SQL Server Management Studio to develop SQL queries. The SQL query development is effective and supported by features, such as the query analyzer and IntelliSense.

When developing LINQ queries against databases it is important to find a similar way to quickly develop query expressions. One very effective tool for developing LINQ queries is a product called LINQPad.6 LINQPad is not open source, but the standard edition is free to download and use. The auto-completion feature and the professional edition cost extra.7

Figure 7-1 shows the main interface to LINQPad. This screenshot shows how the MvcMusicStore database is queried using the LINQ query syntax. In a manner similar to SQL Enterprise, LINQPad lets you interactively query databases, in either the LINQ query syntax or method syntax.

__________

6 For more information see www.linqpad.net.

7 For those addicted to IntelliSense, LINQPad auto-completion is worth the additional cost.

images

Figure 7-1. Developing queries with LINQPad 4

With the introduction of LINQ, the concept of a deferred query became important to understand and appreciate. Deferred query execution is often misunderstood.8 This can lead to unanticipated performance problems. It is important to recognize how you can make deferred queries work for you. Listing 7-14 shows a series of LINQ statements that query the MvcMusicStore database. Although there are two LINQ query statements, the query does not execute until the line with the foreach statement.

Listing 7-14. Deferred Query Execution in LINQ

var albums = Albums
        .Where (x => x.Price < 9.00m);

var results = albums
        .Where (n => n.Artist.Name.StartsWith("B"))
        .Select (n =>
        new
        {
                AlbumTitle = n.Title,
                ArtistName = n.Artist.Name
        });

foreach (var result in results)
{
        Console.WriteLine(
                ""{0}", by {1}",
                result.AlbumTitle,
                result.ArtistName);
}

__________

Since the query execution is deferred, the LINQ provider (LINQ to SQL) combines the queries that underlie the two LINQ statements and executes only one query. In effect, the query combination is equivalent to this single SQL query, which returns 11 rows:

SELECT [t0].[Title] AS [AlbumTitle], [t1].[Name] AS [ArtistName]
FROM [Album] AS [t0]
    INNER JOIN [Artist] AS [t1]
    ON [t1].[ArtistId] = [t0].[ArtistId]
WHERE ([t1].[Name] LIKE 'B%')
    AND ([t0].[Price] < 9.00)

Unfortunately, developers who do not appreciate deferred query execution might write the same logic, but in a way that breaks the deferred query execution, as shown in Listing 7-15. The database query now executes on the line with the ToList method call.

Listing 7-15. LINQ Statements That Break Deferred Query Execution

var albums = Albums
        .Where (x => x.Price < 9.00m)
        .ToList();

var results = albums
        .Where (n => n.Artist.Name.StartsWith("B"))
        .Select (n =>
        new
        {
                AlbumTitle = n.Title,
                ArtistName = n.Artist.Name
        });

foreach (var result in results)
{
        Console.WriteLine(
                ""{0}", by {1}",
                result.AlbumTitle,
                result.ArtistName);
}

The query execution is no longer deferred. With the call to the ToList method, the LINQ provider queries the database with a SQL query similar to the following, which returns 246 rows:

SELECT [t0].*
FROM [Album] AS [t0]
WHERE [t0].[Price] < 9.00

The remaining query logic acts upon the objects that are retrieved into memory for this first query. It is easy to see how by not appreciating deferred query execution code can result in database queries that are very inefficient. In this simple example, the database query brought back hundreds of rows, but less than a dozen rows met the combined criteria. There are examples of how improperly written LINQ statements returned tens of thousands of unnecessary rows from the database, which destroyed performance9.

images Practice 7-10 Understand Deferred Query Execution to Use the Full Power of LINQ

Database LINQ providers, including Entity Framework, NHibernate, and LINQ to SQL, support deferred execution. These providers do a lot of work before executing a query to create an optimized SQL statement to send an efficient query to the database. It is important to write LINQ queries with a full appreciation for how deferred query execution optimizes these queries.

The power of LINQ is also found when working with Reactive Extensions in .NET 4.0, more commonly abbreviated as Rx. Rx is an innovation from Microsoft researcher Erik Meijer, who is cited by many as the father of LINQ. Rx brings a new “push model” to .NET for event-subscription programming, such as user interface and network events.10 The principal Rx types are the IObservable<T> and IObserver<T> interfaces, which allows the provider of data to “push” to subscribers, who react to the newly available data. In the pull model you enumerate available data. In the push model you subscribe to observable data events. LINQ is designed to be applicable for this inverse model. Basically, LINQ to Rx is written to use LINQ constructs that give you the ability to query Rx providers and use LINQ in reactive programming applications. At Microsoft Research, DryadLINQ is further demonstrating the power of LINQ and how it is applicable to distributed computing on thousands of servers for large-scale data parallel applications.11

Summary

In this chapter you learned about many powerful C# language constructs. These language features have added new and better ways to write better software. In many ways the code is easier to write, easier to read, and more maintainable.

In the next chapter you will learn about automated testing and many related better practices.

__________

9 Scott Hanselman describes a curious case of performance problems when the query execution was not deferred: www.hanselman.com/blog/TheWeeklySourceCode52YouKeepUsingThatLINQIDunnaThinkItMeansWhatYouThinkItMeans.aspx

10 For a nice introduction to LINQ to Rx see section 12.5 of Jon Skeet, C# in Depth (2nd Edition) (Greenwich, CT: Manning, 2011).

..................Content has been hidden....................

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