O'Reilly logo

C# 3.0 in a Nutshell, 3rd Edition by Ben Albahari, Joseph Albahari

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

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

Start Free Trial

No credit card required

Chapter 1. Introducing C# and the .NET Framework

C# is a general-purpose, type-safe, object-oriented programming language. The goal of the language is programmer productivity. To this end, the language balances simplicity, expressiveness, and performance. The chief architect of the language since its first version is Anders Hejlsberg (creator of Turbo Pascal and architect of Delphi). The C# language is platform-neutral, but it was written to work well with the Microsoft .NET Framework.

Object Orientation

C# is a rich implementation of the object-orientation paradigm, which includes encapsulation, inheritance, and polymorphism. Encapsulation means creating a boundary around an object, to separate its external (public) behavior from its internal (private) implementation details. The distinctive features of C# from an object-oriented perspective are:

Unified type system

The fundamental building block in C# is an encapsulated unit of data and functions called a type. C# has a unified type system, where all types ultimately share a common base type. This means that all types, whether they represent business objects or are primitive types such as integrals, share the same basic set of functionality. For example, any type can be converted to a string by calling its ToString method.

Classes and interfaces

In the pure objected-oriented paradigm, the only kind of type is a class. In C#, there are several other kinds of types, one of which is an interface (similar to Java interfaces). An interface is like a class except it is only a definition for a type, not an implementation. It's particularly useful in scenarios where multiple inheritance is required (unlike languages such as C++ and Eiffel, C# does not support multiple inheritance of classes).

Properties, methods, and events

In the pure object-oriented paradigm, all functions are methods (this is the case in Smalltalk). In C#, methods are only one kind of function member, which also includes properties and events (there are others too). Properties are function members that encapsulate a piece of an object's state, such as a button's color or a label's text. Events are function members that simplify acting on object state changes.

Type Safety

C# is primarily a type-safe language, meaning that types can interact only through protocols they define, thereby ensuring each type's internal consistency. For instance, C# prevents you from interacting with a string type as though it were an integer type.

More specifically, C# supports static typing, meaning that the language enforces type safety at compile time. This is in addition to dynamic type safety, which the .NET CLR enforces at runtime.

Static typing eliminates a large class of errors before a program is even run. It shifts the burden away from runtime unit tests onto the compiler to verify that all the types in a program fit together correctly. This makes large programs much easier to manage, more predictable, and more robust. Furthermore, static typing allows tools such as IntelliSense in Visual Studio .NET to help you write a program, since it knows for a given variable what type it is, and hence what methods you can call on that variable.

C# is called a strongly typed language because its type rules (whether enforced statically or dynamically) are very strict. For instance, you cannot call a function that's designed to accept an integer with a floating-point number, unless you first explicitly convert the floating-point number to an integer. This helps prevent mistakes.

Strong typing also plays a role in enabling C# code to run in a sandbox—an environment where every aspect of security is controlled by the host. In a sandbox, it is important that you cannot arbitrarily corrupt the state of an object by bypassing its type rules.

Memory Management

C# relies on the runtime to perform automatic memory management. The CLR has a garbage collector that executes as part of your program, reclaiming memory for objects that are no longer referenced. This frees programmers from explicitly deallocating the memory for an object, eliminating the problem of corrupt pointers, encountered in languages such as C++.

C# does not eliminate pointers: it merely makes them unnecessary for most programming tasks. For performance-critical hotspots and interoperability, pointers may be used, but they are permitted only in blocks that are explicitly marked unsafe.

Platform Support

C# is typically used for writing code that runs on Windows platforms. Although Microsoft standardized the C# language and the CLR through ECMA, the total amount of resources (both inside and outside of Microsoft) dedicated to supporting C# on non-Windows platforms is relatively small. This means that languages such as Java are sensible choices when multiplatform support is the name of the game. Having said this, C# can be used to write cross-platform code in the following scenarios:

  • C# code may run on the server and dish up DHTML that can run on any platform. This is precisely the case for ASP.NET.

  • C# code may run on a runtime other than the Microsoft Common Language Runtime. The most notable example is the Mono project, which has its own C# compiler and runtime, running on Linux, Solaris, Mac OS X, and Windows.

  • C# code may run on a host that supports Microsoft Silverlight (supported for Windows and Mac OS X). This is a new technology that is analogous to Adobe's Flash Player.

C#'s Relationship with the CLR

C# depends on a runtime equipped with a host of features such as automatic memory management and exception handling. The design of C# closely maps to the design of the CLR, which provides these runtime features (although C# is technically independent of the CLR). Furthermore, the C# type system maps closely to the CLR type system (e.g., both share the same definitions for primitive types).

The CLR and .NET Framework

The .NET Framework consists of a runtime called the Common Language Runtime (CLR) and a vast set of libraries. The libraries consist of core libraries (which this book is concerned with) and applied libraries, which depend on the core libraries. Figure 1-1 is a visual overview of those libraries (and also serves as a navigational aid to the book).

This depicts the topics covered in this book and the chapters in which they are found. The names of specialized frameworks and class libraries beyond the scope of this book are grayed out and displayed outside the boundaries of The Nutshell.

Figure 1-1. This depicts the topics covered in this book and the chapters in which they are found. The names of specialized frameworks and class libraries beyond the scope of this book are grayed out and displayed outside the boundaries of The Nutshell.

The CLR is the runtime for executing managed code. C# is one of several managed languages that get compiled into managed code. Managed code is packaged into an assembly, in the form of either an executable file (an .exe) or a library (a .dll), along with type information, or metadata.

Managed code is represented in Intermediate Language or IL. When the CLR loads an assembly, it converts the IL into the native code of the machine, such as x86. This conversion is done by the CLR's JIT (Just-In-Time) compiler. An assembly retains almost all of the original source language constructs, which makes it easy to inspect and even generate code dynamically.

Tip

Lutz Roeder's .NET Reflector application is an invaluable tool for examining the contents of an assembly (you can also use it as a decompiler).

The CLR performs as a host for numerous runtime services. Examples of these services include memory management, the loading of libraries, and security services.

The CLR is language-neutral, allowing developers to build applications in multiple languages (e.g., C#, Visual Basic .NET, Managed C++, Delphi.NET, Chrome .NET, and J#).

The .NET Framework consists of libraries for writing just about any Windows-based application. Chapter 5 gives an overview of the .NET Framework libraries.

What's New in C# 3.0

C# 3.0 features are centered on Language Integrated Query capabilities, or LINQ for short. LINQ was inspired by the work done on Comega (formerly known as X# and Xen). Erik Meijer was the primary architect who worked closely with Anders Hejlsberg to incorporate that work into C#.

LINQ enables SQL-like queries to be written directly within a C# program and checked statically for correctness. The architecture of LINQ allows queries to execute either locally or remotely. The .NET Framework provides LINQ-enabled APIs across local collections, remote databases, and XML. C# 3.0 features include:

  • Lambda expressions

  • Extension methods

  • Implicitly typed local variables

  • Query comprehensions

  • Anonymous types

  • Object initializers

  • Implicitly typed arrays

  • Automatic properties

  • Partial methods

  • Expression trees

Lambda expressions are like miniature functions created on the fly. They are a natural evolution of anonymous methods introduced in C# 2.0, and in fact, completely subsume the functionality of anonymous methods. For example, the following lambda expression squares an integer:

Func<int,int> square =x => x * x;
Console.WriteLine (square(3));         // 9

For people familiar with functional programming languages, such as Scheme and Haskell, lambda expressions will be a familiar construct. The primary use case in C# is with LINQ queries, such as the following:

string[] names = { "Tom", "Dick", "Harry" };
IEnumerable<string> filteredNames =                // Include only names
  Enumerable.Where (names, n =>n.Length >= 4);    // of >= 4 characters.

Extension methods extend an existing type with new methods, without altering the type's definition. They act as syntactic sugar, making static methods feel like instance methods. Because LINQ's query operators are implemented as extension methods, we can simplify our preceding query as follows:

string[] names = { "Tom", "Dick", "Harry" };
IEnumerable<string> filteredNames = names.Where (n => n.Length >= 4);

Implicitly typed local variables let you omit the variable type in a declaration statement, allowing the compiler to infer it. Since the compiler can determine the type of filteredNames, we can further simplify our query:

var filteredNames = names.Where (n => n.Length == 4);

Query comprehension syntax provides SQL-style syntax for writing queries. Comprehension syntax can simplify certain kinds of queries substantially, as well as serving as syntactic sugar for lambda-style queries. Here's the previous example in comprehension syntax:

var filteredNames =from n in names where n.Length >= 4 select n;

Anonymous types are simple classes created on the fly that are commonly used in the final output of queries:

var query = from n in names where n.Length >= 4
            selectnew {
                         Name = n,
                         Length = n.Length
                       };

Here's a simpler example:

var dude =new { Name = "Bob", Age = 20 };

Implicitly typed arrays eliminate the need to state the array type, when constructing and initializing an array in one step:

var dudes =new[]
{
   new { Name = "Bob", Age = 20 },
   new { Name = "Rob", Age = 30 }
};

Object initializers simplify object construction by allowing properties to be set inline after the constructor call. Object initializers work with both anonymous and named types. The following example shows the new C# 3.0 feature and the equivalent code in C# 2.0:

// C# 3.0
Bunny b1 = new Bunny{ Name="Bo", LikesCarrots=true, LikesHumans=false };

// C# 2.0
Bunny b2 = new Bunny(  );
b2.Name = "Bo";
b2.LikesHumans = false;

Automatic properties cut the work in writing properties that simply get/set a private backing field. In the following example, the compiler automatically generates a private backing field for X:

public class Stock
{
  // C# 3.0:public decimal X { get; set; }

  // C# 2.0:
              private decimal y;
              public decimal Y
              {
                get { return y;  }
                set { y = value; }
              }
}

Partial methods let an auto-generated partial class provide customizable hooks for manual authoring. LINQ to SQL makes use of partial methods for generated classes that map SQL tables. Here is an example of a pair of partial methods, one auto-generated and the other hand-authored:

// PaymentFormGen.cs — auto-generated
partial class PaymentForm
{
  ...partial void ValidatePayment (decimal amount);
}

// PaymentForm.cs — hand-authored
partial class PaymentForm
{
  ...
  partial void ValidatePayment (decimal amount)
  {
    if (amount > 100)
      ...
  }
}

Expression trees are miniature code DOMs that describe lambda expressions. The C# 3.0 compiler generates expression trees when a lambda expression is assigned to the special type Expression<TDelegate>:

Expression<Func<string,bool>> predicate = s => s.Length > 10;

Expression trees make it possible for LINQ queries to execute remotely (e.g., on a database server) because they can be introspected and translated at runtime (e.g., into a SQL statement).

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

Start Free Trial

No credit card required