Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

472 CHAPTER 14 AN INTRODUCTION TO LINQ

Summary

LINQ is a set of related technologies that attempts to provide a single, symmetrical manner to interact with diverse forms of data. As explained over the course of this chapter, LINQ can interact with any type implementing the IEnumerable<T> interface, including simple arrays as well as generic and nongeneric collections of data.

As you have seen over the course of this chapter, working with LINQ technologies is accomplished using several new C# 2008 language features. For example, given the fact that LINQ query expressions can return any number of result sets, it is common to make use of the var keyword to represent the underlying data type. As well, lambda expressions, object initialization syntax, and anonymous types can all be used to build very functional and compact LINQ queries.

More importantly, you have seen how the C# LINQ query operators are simply shorthand notations for making calls on static members of the System.Linq.Enumerable type. As shown, most members of Enumerable operate on Func<T> delegate types, which can take literal method addresses, anonymous methods, or lambda expressions as input to evaluate the query.

P A R T 4

Programming with .NET Assemblies

C H A P T E R 1 5

Introducing .NET Assemblies

Each of the applications developed in this book’s first fourteen chapters were along the lines of traditional “stand-alone” applications, given that all of your custom programming logic was contained within a single executable file (*.exe). However, one major aspect of the .NET platform is the notion of binary reuse, where applications make use of the types contained within various external assemblies (aka code libraries). The point of this chapter is to examine the core details of creating, deploying, and configuring .NET assemblies.

In this chapter, you’ll first learn the construction of .NET namespaces followed by the distinction between single-file and multifile assemblies, as well as “private” and “shared” assemblies. Next, you’ll examine exactly how the .NET runtime resolves the location of an assembly and come to understand the role of the global assembly cache (GAC), application configuration files (*.config files), publisher policy assemblies, and the role of the System.Configuration namespace.

Defining Custom Namespaces

Before diving into the details of assembly deployment and configuration, it is very important to examine the topic of creating custom .NET namespaces. Up to this point in the text, you have been building small test programs leveraging existing namespaces in the .NET universe (System in particular). However, when you build your own custom applications, it can be very helpful to group your related types into custom namespaces. In C#, this is accomplished using the namespace keyword. This is even more important when creating .NET *.dll assemblies, as other developers will need to import your custom namespaces to make use of your types.

Assume you are developing a collection of geometric classes named Square, Circle, and Hexagon. Given their similarities, you would like to group them all together into a common custom namespace. You have two basic approaches. First, you may choose to define each class within a single file (ShapesLib.cs) as follows:

// shapeslib.cs using System;

namespace MyShapes

{

// Circle class

class Circle{ /* Interesting methods... */ }

// Hexagon class

class Hexagon{ /* More interesting methods... */ }

// Square class

class Square{ /* Even more interesting methods... */ }

}

475

476 CHAPTER 15 INTRODUCING .NET ASSEMBLIES

Notice how the MyShapes namespace acts as the conceptual “container” of these types. Alternatively, you can split a single namespace into multiple C# files. To do so, simply wrap the given class definitions in the same namespace:

// circle.cs using System;

namespace MyShapes

{

// Circle class class Circle{ }

}

// hexagon.cs using System;

namespace MyShapes

{

// Hexagon class class Hexagon{ }

}

// square.cs using System;

namespace MyShapes

{

// Square class class Square{ }

}

When another namespace wishes to use objects within a distinct namespace, the using keyword can be used as follows:

// Make use of types defined the MyShape namespace. using System;

using MyShapes;

namespace MyApp

{

class ShapeTester

{

static void Main(string[] args)

{

Hexagon h = new Hexagon(); Circle c = new Circle(); Square s = new Square();

}

}

}

A Type’s Fully Qualified Name

Technically speaking, you are not required to make use of the C# using keyword when declaring a type defined in an external namespace. You could make use of the fully qualified name of the type, which as you recall from Chapter 1 is the type’s name prefixed with the defining namespace:

CHAPTER 15 INTRODUCING .NET ASSEMBLIES

477

// Note we are not "using" MyShapes anymore. using System;

namespace MyApp

{

class ShapeTester

{

static void Main(string[] args)

{

MyShapes.Hexagon h = new MyShapes.Hexagon(); MyShapes.Circle c = new MyShapes.Circle(); MyShapes.Square s = new MyShapes.Square();

}

}

}

Typically there is no need to use a fully qualified name. Not only does it require a greater number of keystrokes, but also it makes no difference whatsoever in terms of code size or execution speed. In fact, in CIL code, types are always defined with the fully qualified name. In this light, the C# using keyword is simply a typing time-saver.

However, fully qualified names can be very helpful (and sometimes necessary) to avoid name clashes that may occur when using multiple namespaces that contain identically named types. Assume you have a new namespace termed My3DShapes, which defines three classes capable of rendering a shape in stunning 3D:

// Another shapes namespace...

using System;

namespace My3DShapes

{

//3D Circle class class Circle{ }

//3D Hexagon class class Hexagon{ }

//3D Square class class Square{ }

}

If you update ShapeTester as was done here, you are issued a number of compile-time errors, because both namespaces define identically named types:

// Ambiguities abound! using System;

using MyShapes; using My3DShapes;

namespace MyApp

{

class ShapeTester

{

static void Main(string[] args)

{

// Which namespace do I reference?

Hexagon h = new Hexagon(); // Compiler error!

Circle

c

=

new

Circle();

//

Compiler

error!

Square

s

=

new

Square();

//

Compiler

error!

}

}

}

478 CHAPTER 15 INTRODUCING .NET ASSEMBLIES

The ambiguity can be resolved using the type’s fully qualified name:

// We have now resolved the ambiguity. static void Main(string[] args)

{

My3DShapes.Hexagon h = new My3DShapes.Hexagon(); My3DShapes.Circle c = new My3DShapes.Circle(); MyShapes.Square s = new MyShapes.Square();

}

Defining using Aliases

The C# using keyword can also be used to create an alias to a type’s fully qualified name. When you do so, you are able to define a token that is substituted with the type’s full name at compile time, for example:

using System; using MyShapes; using My3DShapes;

// Resolve the ambiguity using a custom alias. using The3DHexagon = My3DShapes.Hexagon;

namespace MyApp

{

class ShapeTester

{

static void Main(string[] args)

{

// This is really creating a My3DShapes.Hexagon type.

The3DHexagon h2 = new The3DHexagon();

...

}

}

}

This alternative using syntax can also be used to create an alias to a lengthy namespace. One of the longer namespaces in the base class library would have to be System.Runtime.Serialization. Formatters.Binary, which contains a member named BinaryFormatter. If you wish, you could create an instance of the BinaryFormatter as follows:

using MyAlias = System.Runtime.Serialization.Formatters.Binary;

namespace MyApp

{

class ShapeTester

{

static void Main(string[] args)

{

MyAlias.BinaryFormatter b = new MyAlias.BinaryFormatter();

}

}

}

as well as with a traditional using directive:

using System.Runtime.Serialization.Formatters.Binary;
namespace MyApp
{
class ShapeTester
{
static void Main(string[] args)
{
BinaryFormatter b = new BinaryFormatter();
}
}
}
Note C# also provides a mechanism that can be used to resolve name clashes between identically named namespaces using the namespace alias qualifier (::) and global token. Thankfully, this type of name collision is rare. If you require more information regarding this topic, look up my article “Working with the C# 2.0 Command Line Compiler” from http://msdn.microsoft.com.
Creating Nested Namespaces
When organizing your types, you are free to define namespaces within other namespaces. The .NET base class libraries do so in numerous places to provide an even deeper level of type organization. For example, the Collections namespace is nested within System, to yield System.Collections. If you wish to create a root namespace that contains the existing My3DShapes namespace, you can update your code as follows:
// Nesting a namespace. namespace Chapter15
{
namespace My3DShapes
{
// 3D Circle class class Circle{ }
// 3D Hexagon class class Hexagon{ }
// 3D Square class class Square{ }
}
}
In many cases, the role of a root namespace is simply to provide a further level of scope, and therefore may not define any types directly within its scope (as in the case of the Chapter15 namespace). If this is the case, a nested namespace can be defined using the following compact form:
// Nesting a namespace (take two). namespace Chapter15.My3DShapes
{
// 3D Circle class class Circle{ }
// 3D Hexagon class class Hexagon{ }
// 3D Square class class Square{ }
}

CHAPTER 15 INTRODUCING .NET ASSEMBLIES

479

480 CHAPTER 15 INTRODUCING .NET ASSEMBLIES

Given that you have now nested the My3DShapes namespace within the Chapter15 root namespace, you need to update any existing using directives and type aliases:

using Chapter15.My3DShapes;

using The3DHexagon = Chapter15.My3DShapes.Hexagon;

The “Default Namespace” of Visual Studio 2008

On a final namespace-related note, it is worth pointing out that by default, when you create a new C# project using Visual Studio 2008, the name of your application’s default namespace will be identical to the project name. From this point on, when you insert new items using the Project Add New Item menu selection, types will automatically be wrapped within the default namespace. If you wish to change the name of the default namespace (e.g., to be your company name), simply access the Default namespace option using the Application tab of the project’s Properties window (see Figure 15-1).

Figure 15-1. Configuring the default namespace

With this update, any new item inserted into the project will be wrapped within the IntertechTraining namespace (and, obviously, if another namespace wishes to use these types, the correct using directive must be applied).

Source Code The Namespaces project is located under the Chapter 15 subdirectory.

The Role of .NET Assemblies

.NET applications are constructed by piecing together any number of assemblies. Simply put, an assembly is a versioned, self-describing binary file hosted by the CLR. Now, despite the fact that

.NET assemblies have exactly the same file extensions (*.exe or *.dll) as previous Win32 binaries (including legacy COM servers), they have very little in common under the hood. Thus, to set the stage for the information to come, let’s consider some of the benefits provided by the assembly format.

CHAPTER 15 INTRODUCING .NET ASSEMBLIES

481

Assemblies Promote Code Reuse

As you have been building your Console Applications over the previous chapters, it may have seemed that all of the applications’ functionality was contained within the executable assembly you were constructing. In reality, your applications were leveraging numerous types contained within the always accessible .NET code library, mscorlib.dll (recall that the C# compiler references mscorlib.dll automatically), and in the case of some examples, System.Windows.Forms.dll.

As you may know, a code library (also termed a class library) is a *.dll that contains types intended to be used by external applications. When you are creating executable assemblies, you will no doubt be leveraging numerous system-supplied and custom code libraries as you create the application at hand. Do be aware, however, that a code library need not take a *.dll file extension. It is perfectly possible for an executable assembly to make use of types defined within an external executable file. In this light, a referenced *.exe can also be considered a “code library.”

Regardless of how a code library is packaged, the .NET platform allows you to reuse types in a language-independent manner. For example, you could create a code library in C# and reuse that library in any other .NET programming language. It is possible to not only allocate types across languages, but also derive from them. A base class defined in C# could be extended by a class authored in Visual Basic. Interfaces defined in Pascal .NET can be implemented by structures defined in C#, and so forth. The point is that when you begin to break apart a single monolithic executable into numerous .NET assemblies, you achieve a language-neutral form of code reuse.

Assemblies Establish a Type Boundary

To begin this chapter, you learned about the formalities behind .NET namespaces. Recall that a type’s fully qualified name is composed by prefixing the type’s namespace (e.g., System) to its name (e.g., Console). Strictly speaking however, the assembly in which a type resides further establishes a type’s identity. For example, if you have two uniquely named assemblies (say, MyCars.dll and

YourCars.dll) that both define a namespace (CarLibrary) containing a class named SportsCar, they are considered unique types in the .NET universe.

Assemblies Are Versionable Units

.NET assemblies are assigned a four-part numerical version number of the form <major>.<minor>. <build>.<revision> (if you do not explicitly provide a version number, the assembly is automatically assigned a version of 0.0.0.0). This number, in conjunction with an optional public key value, allows multiple versions of the same assembly to coexist in harmony on a single machine. Formally speaking, assemblies that provide public key information are termed strongly named. As you will see in this chapter, using a strong name, the CLR is able to ensure that the correct version of an assembly is loaded on behalf of the calling client.

Assemblies Are Self-Describing

Assemblies are regarded as self-describing in part because they record every external assembly it must have access to in order to function correctly. Thus, if your assembly requires System.Windows. Forms.dll and System.Drawing.dll, they will be documented in the assembly’s manifest. Recall from Chapter 1 that a manifest is a blob of metadata that describes the assembly itself (name, version, required external assemblies, etc.).

In addition to manifest data, an assembly contains metadata that describes the composition (member names, implemented interfaces, base classes, constructors, and so forth) of every contained type. Given that an assembly is documented in such vivid detail, the CLR does not consult