Book Review: C# 7.0 in a Nutshell

The latest update in the C# in a Nutshell series, it obviously covers C# 7.0. With 1037 pages it is a massive volume, most of it can be used as a reference book, and many of the chapters are not important unless you have very specific needs. The topic in each chapter is covered in significant detail, you will be hard-pressed to find a detail that is not covered I think.

It can be read, cover to cover, but I would recommend to read the first 4 chapters and pick from the remaining based on interest or need. The detail level and reference book type of writing make the book dry to read, so be warned!

My notes from the book are collected here, I hope they will be useful to someone.

Chapter 1 and 2:  A long enumeration of language constructs and type definitions. Like while, for, foreach, do-while, int, reference types, values types and so on. Good read if you need to bridge from another language to C#, but if you have done any C# it should be well known.

Chapter 3: Creating types in C#
C# has quite a few special things going on in this area.

Deconstructing method:

var rect = new Rectangle (3, 4);
(float width, float height) = rect; // Deconstruction

Named parameters: the names of the variables in any method can be used when calling, making the order not important.

var r1 = new Rectangle(height: 4, width: 3);

Properties have the same syntax as fields from the calling side.

public class Rectangle
{
  int height;       // The private "backing" field
  public int Height // The public property
  {
    get { return height; }
    set { height = value; }
  }
}

Properties are read-only if only a getter is created.

The example above can be shortened using an automatic property, where the backing field is created automatically.

public class Rectangle
{
  public decimal Height{ get; set; }
}

Another way is to use expression-bodied properties.

public int Worth
{
  get => currentPrice * sharesOwned;
  set => sharesOwned = value / currentPrice;
}

And if only a getter is needed it can be shortened:

public decimal Worth => currentPrice * sharesOwned;

Indexers allow the syntax obj[index] 

class Sentence
{
  string[] words = "The quick brown fox".Split();
  public string this [int wordNum]      // indexer
  { 
    get { return words [wordNum];  }
    set { words [wordNum] = value; }
  }
}

Partial types allow a type definition to be split across multiple files. Every definition must contain the partial keyword. Often used for autogenerated content. Partial methods are kind of similar to abstract methods/interfaces, it allows one partial class to define a method and another to implement it.

Flag enums used to implement flags that can be combined with binary operators.

[Flags]
public enum BorderSides
{
  None=0,
  Left=1, Right=2, Top=4, Bottom=8,
  LeftRight = Left | Right, 
  TopBottom = Top  | Bottom,
  All       = LeftRight | TopBottom
}

Chapter 4: Advanced C#
There are many features in the language that what you would expect, like operator overloading, attributes and so on.

Delegates are a type that can contain a method. 

delegate int Transformer (int x);

class Test
{
  static void Main()
  {
    Transformer t = Square;          // Create delegate instance
    int result = t(3);               // Invoke delegate
    Console.WriteLine (result);      // 9
  }
  static int Square (int x) => x * x;
}

The t(3) is shorthand for t.Invoke(3). Since the delegate is assigned at runtime it can be used to write plugin methods.

A delegate type can contain multiple methods.

SomeDelegate d = SomeMethod1;
d += SomeMethod2;

All methods are called in the order they are added. Instance methods can also be used, the delegate will contain a reference to the instance.

Events: using delegates we can implement broadcaster/subscriber pattern, this is such a common pattern that it has been formalized into the language.

public delegate void PriceChangedHandler (decimal oldPrice,
                                          decimal newPrice);
public class Stock
{
  string symbol;
  decimal price;

  public Stock (string symbol) { this.symbol = symbol; }

  public event PriceChangedHandler PriceChanged;

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;      // Exit if nothing has changed
      decimal oldPrice = price;
      price = value;
      if (PriceChanged != null)           // If invocation list not
        PriceChanged (oldPrice, price);   // empty, fire event.
    }
  }
}

Lambda expressions: nothing new here, functions similar to Javascript and PHP

Exception filters: a cool way to avoid an exception block with if-logic to select the correct way to handle the exception.

catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{ ... }
catch (WebException ex) when (ex.Status == WebExceptionStatus.SendFailure)
{ ... }

Iterators/generators: Using the yield keyword. Multiple yields can be used, and when yield break is hit the generator stops.

static IEnumerable<string> Foo (bool breakEarly)
{
  yield return "One";
  yield return "Two";

  if (breakEarly)
    yield break;

  yield return "Three";
}

Nullable types:  We can instruct the compiler that a value type can be null, using the “?” operator.

int? i = null;

Extension methods: C# allows us to add new methods to existing classes using a simple static class as shown here:

public static class StringHelper
{
  public static bool IsCapitalized (this string s)
  {
    if (string.IsNullOrEmpty(s)) return false;
    return char.IsUpper (s[0]);
  }
}
...
Console.WriteLine ("Perth".IsCapitalized()); // using the extension method

Anonymous types: the compiler creates an appropriate type when compiling.

var dude = new { Name = "Bob", Age = 23 };
Console.WriteLine(dude.Name); // "Bob"

Tuples: Allows multiple values to be combined into a single element, can be used to return multiple values from a method call.

var bob = ("Bob", 23);    // Allow compiler to infer the element types

Console.WriteLine (bob.Item1);   // Bob
Console.WriteLine (bob.Item2);   // 23

Chapter 5: Framework Overview
Which versions of the .net framework exist and their feature set, a long list of different parts of the framework. Good for reference, but not much else.

Windows Workflow Foundation looks interesting for building software that implements business workflows. I might look further into this at a later time.

Chapter 6: Framework Fundamentals

Description of how encoding works, endless iteration of stuff like equality comparison, good for reference but not an easy read.

One thing to notice, the culture setting affects the string functions.

Chapter 7: Collections

Very boring chapter, reference about everything about collections. Most are exactly as expected.

Chapter 8: LINQ Queries

If you expect to learn how to use LINQ, this chapter will disappoint you. It is very LINQ implementation specific, covering must about how each of the LINQ constructs works behind the scenes. Since each construct is explained more or less separate, it is difficult to get an overview of how to use LINQ.

Both fluent and query syntax are covered. A thing to notice is that is is possible to switch from remote query to local query in the same LINQ statement using AsEnumerable.

Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");

var query = dataContext.MedicalArticles
  .Where (article => article.Topic == "influenza")

  .AsEnumerable()
  .Where (article => wordCounter.Matches (article.Abstract).Count < 100);

The first part is run on the remote SQL server, but when the AsEnumerable() is encountered the collection is instantiated and the rest is run locally. This makes it possible to do filtering that is not supported by the remote source.

There are two SQL backends for LINQ L2S and EF. It seems that EF is the way to go if working with new code.

Chapter 9:  LINQ Operators

Walkthrough of all operators, how they relate to local collections and remote collections. Similar in style to the previous chapter, it is an enumeration of all the operators with does not give a good idea of how to use them.

Chapter 10: LINQ to XML

I find it quite interesting that LINQ can be used to query into XML documents, using the same constructs as the rest of LINQ.

The query part borrows a lot from LINQ, so the concepts are similar. In concept, it is quite similar to XPath but with a different syntax.

It also supports a clever way to generate XML; it does not use the old school DOM objects. But instead, X* classes which makes generating XML much easier.

var styleInstruction = new XProcessingInstruction (
  "xml-stylesheet", "href='styles.css' type='text/css'");

var docType = new XDocumentType ("html",
  "-//W3C//DTD XHTML 1.0 Strict//EN",
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", null);

XNamespace ns = "http://www.w3.org/1999/xhtml";
var root =
  new XElement (ns + "html",
    new XElement (ns + "head",
      new XElement (ns + "title", "An XHTML page")),
    new XElement (ns + "body",
      new XElement (ns + "p", "This is the content"))
  );

var doc =
  new XDocument (
    new XDeclaration ("1.0", "utf-8", "no"),
    new XComment ("Reference a stylesheet"),
    styleInstruction,
    docType,
    root);

doc.Save ("test.html");

Chapter 11: Other XML technologies

Reading and writing XML is of course also possible without LINQ.

Reading XML using something that is not actually a SAX parser but close. It is low level compared to x-dom and in many ways primitive.

It allows good performance that is not possible using X-DOM, but it comes with a cost.

A problem with it is that it is not good at handling elements where the ordering changes. It can be combined with x-dom to get best of both worlds.

Chapter 12: Dispose and Garbage Collection

Implementing IDisposable make it possible to use a using statement to trigger the dispose method.

using (FileStream fs = new FileStream ("myFile.txt", FileMode.Open))
{
  // ... Write to the file ...
}

The overview of garbage collection contains no surprises.

A finalizer is a method that is run on an object just before garbage collection. It often not needed, and has very limited functionality.

If any field contains a type that implements IDisposable, it is a good idea also to implement it in your class, to make sure all dispose methods are called.

An object is not garbage collected if there is any reference to it. But it is possible to create a weak reference to an object. This type of reference is ignored by the garbage collector and an object with only weak references can be garbage collected. It can be used for implementing caches.

Chapter 13: Diagnostics

There are two classes that provide basic logging and assertion functionality. Debug and Trace. Debug class only runs in debug compiles; Trace runs always. Debug can be used for Asserts that is only enabled in debug builds.

Trace is the standard logging framework, has many different listeners to log to any destination.

It is possible to give hints to the debugger about stepping and what to ignore using attributes for example:

[DebuggerStepThrough, DebuggerHidden]
void DoWorkProxy()
{
  // setup...
  DoWork();
  // teardown...
}

To assert performance, Performance Counters for CPU and memory is available. It is possible to create new performance counters(requires admin privileges)

The Stopwatch class implements methods for timing.

Chapter 14: Concurrency and Asynchrony

All the usual problems with concurrency, nothing different for C# compared to other languages.

Tasks are the way to go; it provides a better interface to threads. There exist two types of threads background and foreground threads. Background threads are killed if the main thread finishes. Foreground threads keep running.

Tasks handle exceptions with AggregateException with an InnerException from within the task. Tasks also support continuation to help improve performance.

Coarse-grained concurrency is implemented using Tasks; a thread usually spans multiple method calls.

Fine-grained concurrency is implemented using async and allows single method calls to be treated concurrently.

Synchronization contexts, useful for GUI programs.

Calling an async method does not always execute on a different thread. It can complete synchronously if it knows the answer, for example, if it has a cache.

Passing a CancellationToken to an async call allows the caller to cancel the processing if the result is no longer needed.

Progress reporting is implemented using the IProgress<T> and Progress<T> types. It allows the async code to communicate the progress back to the caller.

Chapter 15: Streams and I/O

Constructed as a three layered structure, Adaptors, decorators and Backing stores. It seems like a well thought out structure.

There is a list of constants to different standard windows dirs.

MemoryMappedFile maps into shared memory. Can be to share data between processes.

Chapter 16: Networking

C# contains a list of already implemented clients for interacting with services.

  • Webclient(HTTP/FTP)
  • HttpClient(Rest/web API),
  • SmtpClient
  • Dns
  • TcpClient
  • UdpClient

Beware that default concurrency limit is 2, which is a bit conservative for my taste.

HttpMessageHandler can be used to mock in unit testing

Remember to set the proxy setting to null else they are autodetected which can be slow.

Chapter 17: Serialization

There are four different serialization mechanisms, data contract, binary, XML serializer and IXmlSerializable

They all have pluggable formatters for binary/XML format.

Both explicit and implicit serialization is supported.

There is the possibility of forward and backwards compatibility for some of the engines but not all.

Lots of options for how to serialize and deserialize, supports almost everything you will ever need as far as I can tell.

Chapter 18: Assemblies

Strong named assemblies gives extra security because they contain a hash signed with a private key, making it hard to mimic.

GAC versions is favored over local versions, complicating things

Satellite assemblies to allow for translation of resources + embedded resources, accessed using GetManifestResourceStream

.resouce files are handled differently from embedded resources.

Chapter 19: Reflection and Metadata

Supports everything you would expect for reflection. Support generating new IL code on the fly.

Chapter 20: Dynamic programming

C# is inherently a strongly typed language. But it does support programming without types. Meaning that types are resolved at runtime.

This feature is used to support scripting languaged like IronPython.

Chapter 21: Security

Code Access Security(CAS) is used to create a sandbox environment but:

Microsoft stated in 2015 that CAS should not be relied upon as a mechanism for enforcing security boundaries (and CAS has been largely excluded from .NET Standard 2.0). This is in spite of the improvements to CAS introduced with CLR 4 in 2010.

Identity and role security are implemented as a standard feature in the framework. It supports both imperative security (checking in code – forces us t remember to do it) and declarative security (most known by attributes).

The cryptographic support supports both hashing and encryption/decryption, using most well-known algorithms.

Chapter 22: Advanced threading

I only skimmed this chapter because I’m primarily working with the web where threading is implicitly exploited by many parallel requests.

Chapter 23: Parallel programming

Also just skimmed this chapter.

PFX (Parallel Framework)

PLINQ ( same syntax as LINQ to parallelize tasks) declarative syntax

Chapter 24: Application Domains

Not used by .net core, so I skipped the chapter.

Chapter 25: Native and COM Interoperability

Calling unmanaged code (.dll’s and similar). I skipped the chapter.

Chapter 26: Regular Expressions

C# regex is based on Perl 5 regex. It makes it very easy to find good examples.

Chapter 27: The Roslyn Compiler

Since C# 6.0 the compiler has been written in C#, it is open source, so it is possible to peek into how the code is processed.

This chapter contains a walkthrough of the compiler architecture. Unless you have a keen interest in compilers, I would skip the chapter.

 


Also published on Medium.