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.
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:
ToString
method.While C# is primarily an object-oriented language, it also borrows from the functional programming paradigm. Specifically:
C# is primarily a type-safe language, meaning that instances of 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 type safety being enforced 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 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# also allows parts of your code to be dynamically typed via the dynamic
keyword (introduced in C# 4.0). However, C# remains a predominantly statically typed language.
C# is also called a strongly typed language because its type rules (whether enforced statically or at runtime) 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.
C# relies on the runtime to perform automatic memory management. The Common Language Runtime 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 incorrect 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
.
Historically, C# was used almost entirely for writing code to run on Windows platforms. Recently, however, Microsoft and other companies have invested in other platforms, including Mac OS X and iOS, and Android. Xamarin™ allows cross-platform C# development for mobile applications, and Portable Class Libraries are becoming increasingly widespread. Microsoft’s ASP.NET 5 is a new web hosting framework that can run either on the .NET Framework or on .NET Core, a new small, fast, open source, cross-platform runtime.
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 Microsoft’s Common Language Runtime (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 predefined types).
The .NET Framework consists of the CLR plus 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).
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.
You can examine and decompile the contents of an IL assembly with tools such as ILSpy, dotPeek (JetBrains) or Reflector (Red Gate).
When writing Windows Store apps, you also now have the option of generating native code directly (“.NET Native”). This improves startup performance and memory usage (which is particularly beneficial on mobile devices) and also runtime performance through static linking and other optimizations.
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#, F#, Visual Basic .NET and Managed C++).
The .NET Framework contains libraries for writing just about any Windows- or web-based application. Chapter 5 gives an overview of the .NET Framework libraries.
C# also interoperates with Windows Runtime (WinRT) libraries. WinRT is an execution interface and runtime environment for accessing libraries in a language-neutral and object-oriented fashion. It ships with Windows 8 and newer and is (in part) an enhanced version of Microsoft’s Component Object Model or COM (see Chapter 25).
Windows 8 and newer ship with a set of unmanaged WinRT libraries that serve as a framework for touch-enabled applications delivered through Microsoft’s application store. (The term WinRT also refers to these libraries.) Being WinRT, the libraries can easily be consumed not only from C# and VB, but C++ and JavaScript.
Some WinRT libraries can also be consumed in normal non-tablet applications. However, taking a dependency on WinRT gives your application a minimum OS requirement of Windows 8.
The WinRT libraries support the new “modern” user interface (for writing immersive touch-first applications), mobile device-specific features (sensors, text messaging and so on), and a range of core functionality that overlaps with parts of the .NET Framework. Because of this overlap, Visual Studio includes a reference profile (a set of .NET reference assemblies) for Windows Store projects that hides the portions of the .NET Framework that overlap with WinRT. This profile also hides large portions of the .NET Framework considered unnecessary for tablet apps (such as accessing a database). Microsoft’s application store, which controls the distribution of software to consumer devices, rejects any program that attempts to access a hidden type.
A reference assembly exists purely to compile against and may have a restricted set of types and members. This allows developers to install the full .NET Framework on their machines while coding certain projects as though they had only a subset. The actual functionality comes at runtime from assemblies in the global assembly cache (see Chapter 18) that may superset the reference assemblies.
Hiding most of the .NET Framework eases the learning curve for developers new to the Microsoft platform, although there are two more important goals:
It sandboxes applications (restricts functionality to reduce the impact of malware). For instance, arbitrary file access is forbidden, and there the ability to start or communicate with other programs on the computer is extremely restricted.
It allows low-powered Windows RT-only tablets to ship with a reduced .NET Framework, lowering the OS footprint.
What distinguishes WinRT from ordinary COM is that WinRT projects its libraries into a multitude of languages, namely C#, VB, C++ and JavaScript, so that each language sees WinRT types (almost) as though they were written especially for it. For example, WinRT will adapt capitalization rules to suit the standards of the target language, and will even remap some functions and interfaces. WinRT assemblies also ship with rich metadata in .winmd files, which have the same format as .NET assembly files, allowing transparent consumption without special ritual. In fact, you might even be unaware that you’re using WinRT rather than .NET types, aside of namespace differences. Another clue is that WinRT types are subject to COM-style restrictions; for instance, they offer limited support for inheritance and generics.
WinRT does not supersede the full .NET Framework. The latter is still recommended (and necessary) for standard desktop and server-side development, and has the following advantages:
C# 6.0’s biggest new feature is that the compiler has been completely rewritten in C#. Known as project “Roslyn,” the new compiler exposes the entire compilation pipeline via libraries, allowing you to perform code analysis on arbitrary source code (see Chapter 27). The compiler itself is open source, and the source code is available at github.com/dotnet/roslyn.
In addition, C# 6.0 features a number of minor but significant enhancements, aimed primarily at reducing code clutter.
The null-conditional (“Elvis”) operator (see “Null Operators”, Chapter 2) avoids having to explicitly check for null before calling a method or accessing a type member. In the following example, result
evaluates to null instead of throwing a NullReferenceException
:
System.Text.StringBuilder sb = null; string result = sb?.ToString(); // result is null
Expression-bodied functions (see “Methods”, Chapter 3) allow methods, properties, operators, and indexers that comprise a single expression to be written more tersely, in the style of a lambda expression:
public int TimesTwo (int x) => x * 2; public string SomeProperty => "Property value";
Property initializers (Chapter 3) let you assign an initial value to an automatic property:
public DateTime Created { get; set; } = DateTime.Now;
Initialized properties can also be read-only:
public DateTime Created { get; } = DateTime.Now;
Read-only properties can also be set in the constructor, making it easier to create immutable (read-only) types.
Index initializers (Chapter 4) allow single-step initialization of any type that exposes an indexer:
new Dictionary<int,string>() { [3] = "three", [10] = "ten" }
String interpolation (see “String Type”, Chapter 2) offers a succinct alternative to string.Format
:
string s = $"It is {DateTime.Now.DayOfWeek} today";
Exception filters (see “try Statements and Exceptions”, Chapter 4) let you apply a condition to a catch block:
try { new WebClient().DownloadString("http://asef"); } catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout) { ... }
The using static
(see “Namespaces”, Chapter 2) directive lets you import all the static members of a type, so that you can use those members unqualified:
using static System.Console; ... WriteLine ("Hello, world"); // WriteLine instead of Console.WriteLine
The nameof
(Chapter 3) operator returns the name of a variable, type or other symbol as a string. This avoids breaking code when you rename a symbol in Visual Studio:
int capacity = 123; string x = nameof (capacity); // x is "capacity" string y = nameof (Uri.Host); // y is "Host"
And finally, you’re now allowed to await
inside catch
and finally
blocks.
C# 5.0’s big new feature was support for asynchronous functions via two new keywords, async
and await
. Asynchronous functions enable asynchronous continuations, which make it easier to write responsive and thread-safe, rich-client applications. They also make it easy to write highly concurrent and efficient I/O-bound applications that don’t tie up a thread resource per operation.
We cover asynchronous functions in detail in Chapter 14.
The features new to C# 4.0 were:
Dynamic binding
Optional parameters and named arguments
Type variance with generic interfaces and delegates
COM interoperability improvements
Dynamic binding (Chapters 4 and 20) defers binding—the process of resolving types and members—from compile time to runtime and is useful in scenarios that would otherwise require complicated reflection code. Dynamic binding is also useful when interoperating with dynamic languages and COM components.
Optional parameters (Chapter 2) allow functions to specify default parameter values so that callers can omit arguments, and named arguments allow a function caller to identify an argument by name rather than position.
Type variance rules were relaxed in C# 4.0 (Chapters 3 and 4), such that type parameters in generic interfaces and generic delegates can be marked as covariant or contravariant, allowing more natural type conversions.
COM interoperability (Chapter 25) was enhanced in C# 4.0 in three ways. First, arguments can be passed by reference without the ref
keyword (particularly useful in conjunction with optional parameters). Second, assemblies that contain COM interop types can be linked rather than referenced. Linked interop types support type equivalence, avoiding the need for Primary Interop Assemblies and putting an end to versioning and deployment headaches. Third, functions that return COM-Variant types from linked interop types are mapped to dynamic
rather than object
, eliminating the need for casting.
The features added to C# 3.0 were mostly centered on Language Integrated Query capabilities, or LINQ for short. LINQ enables queries to be written directly within a C# program and checked statically for correctness, and to query both local collections (such as lists or XML documents) or remote data sources (such as a database). The C# 3.0 features added to support LINQ comprised implicitly typed local variables, anonymous types, object initializers, lambda expressions, extension methods, query expressions, and expression trees.
Implicitly typed local variables (var
keyword, Chapter 2) let you omit the variable type in a declaration statement, allowing the compiler to infer it. This reduces clutter as well as allowing anonymous types (Chapter 4), which are simple classes created on the fly that are commonly used in the final output of LINQ queries. Arrays can also be implicitly typed (Chapter 2).
Object initializers (Chapter 3) simplify object construction by allowing properties to be set inline after the constructor call. Object initializers work with both named and anonymous types.
Lambda expressions (Chapter 4) are miniature functions created by the compiler on the fly and are particularly useful in “fluent” LINQ queries (Chapter 8).
Extension methods (Chapter 4) extend an existing type with new methods (without altering the type’s definition), making static methods feel like instance methods. LINQ’s query operators are implemented as extension methods.
Query expressions (Chapter 8) provide a higher-level syntax for writing LINQ queries that can be substantially simpler when working with multiple sequences or range variables.
Expression trees (Chapter 8) are miniature code DOMs (Document Object Models) that describe lambda expressions assigned to the special type Expression<TDelegate>
. 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).
C# 3.0 also added automatic properties and partial methods.
Automatic properties (Chapter 3) cut the work in writing properties that simply get
/set
a private backing field by having the compiler do that work automatically. Partial methods (Chapter 3) let an auto-generated partial class provide customizable hooks for manual authoring which “melt away” if unused.