Chapter 13
Additional C# Techniques

Wrox.com Code Downloads for this Chapter

You can find the wrox.com code downloads for this chapter at www.wrox.com/go/beginningvisualc#2015programming on the Download Code tab. The code is in the Chapter 13 download and individually named according to the names throughout the chapter.

In this chapter, you continue exploring the C# language by looking at a few bits and pieces that haven't quite fit in elsewhere. Anders Hejlsberg (the inventor of C#) and others at Microsoft continue to update and refine the language. At the time of this writing, the most recent changes are part of version 6 of the C# language, which is released as part of the Visual Studio 2015 product line, along with .NET 4.6. At this point in the book, you might be wondering what else could be needed; indeed, previous versions of C# lack little in terms of functionality. However, this doesn't mean that it isn't possible to make some aspects of C# programming easier, or that the relationships between C# and other technologies can't be streamlined.

You also make some final modifications to the CardLib code that you've been building in the last few chapters, and even use CardLib to create a card game.

The : : Operator and the Global Namespace Qualifier

The : : operator provides an alternative way to access types in namespaces. This might be necessary if you want to use a namespace alias and there is ambiguity between the alias and the actual namespace hierarchy. If that's the case, then the namespace hierarchy is given priority over the namespace alias. To see what this means, consider the following code:

using MyNamespaceAlias = MyRootNamespace.MyNestedNamespace;
namespace MyRootNamespace
{
   namespace MyNamespaceAlias
   {
      public class MyClass {}
   }
   namespace MyNestedNamespace
   {
      public class MyClass {}
   }
}

Code in MyRootNamespace might use the following to refer to a class:

MyNamespaceAlias.MyClass

The class referred to by this code is the MyRootNamespace.MyNamespaceAlias.MyClass class, not the MyRootNamespace.MyNestedNamespace.MyClass class. That is, the namespace MyRootNamespace.MyNamespaceAlias has hidden the alias defined by the using statement, which refers to MyRootNamespace.MyNestedNamespace. You can still access the MyRootNamespace.MyNestedNamespace namespace and the class contained within, but it requires different syntax:

MyNestedNamespace.MyClass

Alternatively, you can use the : : operator:

MyNamespaceAlias::MyClass

Using this operator forces the compiler to use the alias defined by the using statement, and therefore the code refers to MyRootNamespace.MyNestedNamespace.MyClass.

You can also use the keyword global with the : : operator, which is essentially an alias to the top-level, root namespace. This can be useful to make it clearer which namespace you are referring to, as shown here:

global::System.Collections.Generic.List<int>

This is the class you'd expect it to be, the generic List<T> collection class. It definitely isn't the class defined with the following code:

namespace MyRootNamespace
{
   namespace System
   {
      namespace Collections
      {
         namespace Generic
         {
            class List<T> {}
         }
      }
   }
}

Of course, you should avoid giving your namespaces names that already exist as .NET namespaces, although similar problems can arise in large projects, particularly if you are working as part of a large team. Using the : : operator and the global keyword might be the only way you can access the types you want.

Custom Exceptions

Chapter 7 covered exceptions and explained how you can use trycatchfinally blocks to act on them. You also saw several standard .NET exceptions, including the base class for exceptions, System.Exception. Sometimes it's useful to derive your own exception classes from this base class for use in your applications, instead of using the standard exceptions. This enables you to be more specific with the information you send to whatever code catches the exception, and it enables catching code to be more specific about which exceptions it handles. For example, you might add a new property to your exception class that permits access to some underlying information, making it possible for the exception's receiver to make the required changes, or just provide more information about the exception's cause.

Adding Custom Exceptions to CardLib

How to use custom exceptions is, once again, best illustrated by upgrading the CardLib project. The Deck.GetCard() method currently throws a standard .NET exception if an attempt is made to access a card with an index less than 0 or greater than 51, but you'll modify that to use a custom exception.

First, you need to create a new class library project called Ch13CardLib, save it in the BegVCSharpChapter13 directory, and copy the classes from Ch12CardLib as before, changing the namespace to Ch13CardLib as applicable. Next, define the exception. You do this with a new class defined in a new class file called CardOutOfRangeException.cs, which you can add to the Ch13CardLib project with Project1Add Class (you can find this code in Ch13CardLibCardOutOfRangeException.cs):

   public class CardOutOfRangeException : Exception
   {
      private Cards deckContents;
      public Cards DeckContents
      {
         get { return deckContents; }
      }
      public CardOutOfRangeException(Cards sourceDeckContents)
         : base("There are only 52 cards in the deck.")
      {
         deckContents = sourceDeckContents;
      }
   }

An instance of the Cards class is required for the constructor of this class. It allows access to this Cards object through a DeckContents property and supplies a suitable error message to the base Exception constructor so that it is available through the Message property of the class.

Next, add code to throw this exception to Deck.cs, replacing the old standard exception (you can find this code in Ch13CardLibDeck.cs):

      public Card GetCard(int cardNum)
      {
         if (cardNum >= 0 && cardNum <= 51)
            return cards[cardNum];
         else
            throw new CardOutOfRangeException(cards.Clone() as Cards);
      }

The DeckContents property is initialized with a deep copy of the current contents of the Deck object, in the form of a Cards object. This means that you see the contents at the point where the exception was thrown, so subsequent modification to the deck contents won't “lose” this information.

To test this, use the following client code (you can find this code in in Ch13CardClientProgram.cs):

Deck deck1 = new Deck();
try
{
   Card myCard = deck1.GetCard(60);
}
catch (CardOutOfRangeException e)
{
   WriteLine(e.Message);
   WriteLine(e.DeckContents[0]);
}
ReadKey();

This code results in the output shown in Figure 13.1.

Command prompt window displaying texts for CardOutOfRangeException.cs “There are only 52 cards in the deck. The Ace of Clubs.”

Figure 13.1

Here, the catching code has written the exception Message property to the screen. You also displayed the first card in the Cards object obtained through DeckContents, just to prove that you can access the Cards collection through your custom exception object.

Events

This section covers one of the most frequently used OOP techniques in .NET: events. You start, as usual, with the basics — looking at what events actually are. After that, you'll see some simple events in action and learn what you can do with them. Then, you learn how you can create and use events of your own.

In this chapter you'll complete your CardLib class library by adding an event. Finally, because this is the last port of call before arriving at some advanced topics, you'll have a bit of fun creating a card game application that uses this class library.

What Is an Event?

Events are similar to exceptions in that they are raised (thrown) by objects, and you can supply code that acts on them. However, there are several important differences, the most important of which is that there is no equivalent to the trycatch structure for handling events. Instead, you must subscribe to them. Subscribing to an event means supplying code that will be executed when an event is raised, in the form of an event handler.

Many handlers can be subscribed to a single event, all of which are called when the event is raised. This can include event handlers that are part of the class of the object that raises the event, but event handlers are just as likely to be found in other classes.

Event handlers themselves are simply methods. The only restriction on an event handler method is that it must match the return type and parameters required by the event. This restriction is part of the definition of an event and is specified by a delegate.

The basic sequence of processing is as follows: First, an application creates an object that can raise an event. For example, suppose an instant messaging application creates an object that represents a connection to a remote user. That connection object might raise an event when a message arrives through the connection from the remote user (see Figure 13.2).

Flow diagram displaying two labeled boxes (Application and Connection) with an arrow labeled Creates.

Figure 13.2

Next, the application subscribes to the event. Your instant messaging application would do this by defining a method that could be used with the delegate type specified by the event, passing a reference to this method to the event. The event handler method might be a method on another object, such as an object representing a display device to show instant messages when they arrive (see Figure 13.3).

Same image as in Figure 13.2 except arrow directs to the added box (at the bottom) labeled Display. Displayhas connecting arrow with a circle which directs to the Connection.

Figure 13.3

When the event is raised, the subscriber is notified. When an instant message arrives through the connection object, the event handler method on the display device object is called. Because you are using a standard method, the object that raises the event can pass any relevant information via parameters, making events very versatile. In the example case, one parameter might be the text of the instant message, which the event handler could display on the display device object. This is shown in Figure 13.4.

Schematic displaying three boxes, namely, Application, Connection, and Display, with Connection having box labeled Raises Event with arrow depicting calls pointing to a common circle with Display.

Figure 13.4

Handling Events

As previously discussed, to handle an event you need to subscribe to it by providing an event handler method whose return type and parameters match that of the delegate specified for use with the event. The following example uses a simple timer object to raise events, which results in a handler method being called.

Defining Events

Now it's time to define and use your own events. The following Try It Out implements an example version of the instant messaging scenario introduced earlier in this chapter, creating a Connection object that raises events that are handled by a Display object.

Multipurpose Event Handlers

The delegate you saw earlier, for the Timer.Elapsed event, contained two parameters that are of a type often seen in event handlers:

  • object source — A reference to the object that raised the event
  • ElapsedEventArgs e — Parameters sent by the event

The reason the object type parameter is used in this event, and indeed in many other events, is that you often need to use a single event handler for several identical events generated by different objects and still tell which object generated the event.

To explain and illustrate this concept, the next Try It Out extends the last example a little.

The EventHandler and Generic EventHandler<T> Types

In most cases, you will follow the pattern outlined in the previous section and use event handlers with a void return type and two parameters. The first parameter will be of type object, and will be the event source. The second parameter will be of a type that derives from System.EventArgs, and will contain any event arguments. As this is so common, .NET provides two delegate types to make it easier to define events: EventHandler and EventHandler<T>. Both of these are delegates that use the standard event handler pattern. The generic version enables you to specify the type of event argument you want to use.

In the previous Try It Out, you saw this in action as you used the generic EventHandler<T> delegate type as follows:

   public class Connection
   {
      public event EventHandler<MessageArrivedEventArgs> MessageArrived;
      …
   }

This is obviously a good thing to do because it simplifies your code. In general, it is best practice to use these delegate types whenever you define an event. Note that if you have an event that doesn't need event argument data, you can still use the EventHandler delegate type. You can simply pass EventArgs.Empty as the argument value.

Return Values and Event Handlers

All the event handlers you've seen so far have had a return type of void. It is possible to provide a return type for an event, but this can lead to problems because a given event can result in several event handlers being called. If all of these handlers return a value, then it can be unclear which value was actually returned.

The system deals with this by allowing you access to only the last value returned by an event handler. That will be the value returned by the last event handler to subscribe to an event. Although this functionality might be of use in some situations, it is recommended that you use void type event handlers, and avoid out type parameters (which would lead to the same ambiguity regarding the source of the value returned by the parameter).

Anonymous Methods

Instead of defining event handler methods, you can choose to use anonymous methods. An anonymous method doesn't actually exist as a method in the traditional sense — that is, it isn't a method on any particular class. Instead, an anonymous method is created purely for use as a target for a delegate.

To create an anonymous method, you need the following code:

delegate(parameters)
{
   // Anonymous method code.
};

parameters is a list of parameters matching those of the delegate type you are instantiating, as used by the anonymous method code:

delegate(Connection source, MessageArrivedEventArgs e)
{
   // Anonymous method code matching MessageHandler event in Ch13Ex03.
};

For example, you could use this code to completely bypass the Display.DisplayMessage() method in Ch13Ex03:

         myConnection1.MessageArrived +=
            delegate(Connection source, MessageArrivedEventArgs e)
            {
               WriteLine($"Message arrived from: {source.Name}");
               WriteLine($"Message Text: {e.Message}");
            };

An interesting point about anonymous methods is that they are effectively local to the code block that contains them, and they have access to local variables in this scope. If you use such a variable, then it becomes an outer variable. Outer variables are not disposed of when they go out of scope as other local variables are; instead, they live on until the anonymous methods that use them are destroyed. This might be some time later than you expect, so it's definitely something to be careful about. If an outer variable takes up a large amount of memory, or if it uses resources that are expensive in other ways (for example, resources that are limited in number), then this could cause memory or performance problems.

Expanding and Using CardLib

Now that you've had a look at defining and using events, you can use them in Ch13CardLib. The event you'll add to your library will be generated when the last Card object in a Deck object is obtained by using GetCard, and it will be called LastCardDrawn. The event enables subscribers to reshuffle the deck automatically, cutting down on the processing necessary by a client. The event will use the EventHandler delegate type and will pass as its source a reference to the Deck object, such that the Shuffle() method will be accessible from wherever the handler is. Add the following code to Deck.cs (you can find this code in Ch13CardLibDeck.cs) to define and raise the event:

namespace Ch13CardLib
{
      public class Deck : ICloneable
      {
        public event EventHandler LastCardDrawn;
        …
        public Card GetCard(int cardNum)
        {
           if (cardNum >= 0 && cardNum <= 51)
           {
              if ((cardNum == 51) && (LastCardDrawn != null))
                 LastCardDrawn(this, EventArgs.Empty);
              return cards[cardNum];
           }
           else
              throw new CardOutOfRangeException((Cards)cards.Clone());
        }
        …
       }

This is all the code required to add the event to the Deck class definition.

After spending all this time developing the CardLib library, it would be a shame not to use it. Before finishing this section on OOP in C# and the .NET Framework, it's time to have a little fun and write the basics of a card game application that uses the familiar playing card classes.

As in previous chapters, you'll add a client console application to the Ch13CardLib solution, add a reference to the Ch13CardLib project, and make it the startup project. This application will be called Ch13CardClient.

To begin, you'll create a new class called Player in a new file in Ch13CardClient, Player.cs. You can find this code in Ch13CardClientPlayer.cs in this chapter's online download. This class will contain two automatic properties: Name (a string) and PlayHand (of type Cards). Both of these properties have private set accessors, but despite this the PlayHand provides write-access to its contents, enabling you to modify the cards in the player's hand.

You'll also hide the default constructor by making it private, and supply a public nondefault constructor that accepts an initial value for the Name property of Player instances.

Finally, you'll provide a bool type method called HasWon(), which returns true if all the cards in the player's hand are the same suit (a simple winning condition, but that doesn't matter too much).

Here's the code for Player.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ch13CardLib;
namespace Ch13CardClient
{
   public class Player
   {
      public string Name { get; private set; }
      public Cards PlayHand { get; private set; }
      private Player()
      {
      }
      public Player(string name)
      {
         Name = name;
         PlayHand = new Cards();
      }
      public bool HasWon()
      {
         bool won = true;
         Suit match = PlayHand[0].suit;
         for (int i = 1; i < PlayHand.Count; i++)
         {
            won &= PlayHand[i].suit == match;
         }
         return won;
      }
   }
}

Next, define a class that will handle the card game itself, called Game. This class is found in the file Game.cs of the Ch13CardClient project. The class has four private member fields:

  • playDeck — A Deck type variable containing the deck of cards to use
  • currentCard — An int value used as a pointer to the next card in the deck to draw
  • players — An array of Player objects representing the players of the game
  • discardedCards — A Cards collection for the cards that have been discarded by players but not shuffled back into the deck

The default constructor for the class initializes and shuffles the Deck stored in playDeck, sets the currentCard pointer variable to 0 (the first card in playDeck), and wires up an event handler called Reshuffle() to the playDeck.LastCardDrawn event. The handler simply shuffles the deck, initializes the discardedCards collection, and resets currentCard to 0, ready to read cards from the new deck.

The Game class also contains two utility methods: SetPlayers() for setting the players for the game (as an array of Player objects) and DealHands() for dealing hands to the players (seven cards each). The allowed number of players is restricted to between two and seven to ensure that there are enough cards to go around.

Finally, there is a PlayGame() method that contains the game logic itself. You'll come back to this method shortly, after you've looked at the code in Program.cs. The rest of the code in Game.cs is as follows (you can find this code in Ch13CardClientGame.cs):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ch13CardLib;
using static System.Console;
namespace Ch13CardClient
{
   public class Game
   {
      private int currentCard;
      private Deck playDeck;
      private Player[] players;
      private Cards discardedCards;
      public Game()
      {
         currentCard = 0;
         playDeck = new Deck(true);
         playDeck.LastCardDrawn += Reshuffle;
         playDeck.Shuffle();
         discardedCards = new Cards();
      }
      private void Reshuffle(object source, EventArgs args)
      {
         WriteLine("Discarded cards reshuffled into deck.");
         ((Deck)source).Shuffle();
         discardedCards.Clear();
         currentCard = 0;
      }
      public void SetPlayers(Player[] newPlayers)
      {
         if (newPlayers.Length > 7)
            throw new ArgumentException(
               "A maximum of 7 players may play this game.");
         if (newPlayers.Length < 2)
            throw new ArgumentException(
               "A minimum of 2 players may play this game.");
         players = newPlayers;
      }
      private void DealHands()
      {
         for (int p = 0; p < players.Length; p++)
         {
            for (int c = 0; c < 7; c++)
            {
               players[p].PlayHand.Add(playDeck.GetCard(currentCard++));
            }
         }
      }
      public int PlayGame()
      {
         // Code to follow.
      }
   }
}

Program.cs contains the Main() method, which initializes and runs the game. This method performs the following steps:

  1. An introduction is displayed.
  2. The user is prompted for a number of players between 2 and 7.
  3. An array of Player objects is set up accordingly.
  4. Each player is prompted for a name, used to initialize one Player object in the array.
  5. A Game object is created and players are assigned using the SetPlayers() method.
  6. The game is started by using the PlayGame() method.
  7. The int return value of PlayGame() is used to display a winning message (the value returned is the index of the winning player in the array of Player objects).

The code for this follows, with comments added for clarity (you can find this code in Ch13CardClientProgram.cs):

      static void Main(string[] args)
      {
         // Display introduction.
         WriteLine("BenjaminCards: a new and exciting card game.");
         WriteLine("To win you must have 7 cards of the same suit in" +
                           " your hand.");
         WriteLine();
         // Prompt for number of players.
         bool inputOK = false;
         int choice = -1;
         do
         {
            WriteLine("How many players (2–7)?");
            string input = ReadLine();
            try
            {
               // Attempt to convert input into a valid number of players.
               choice = Convert.ToInt32(input);
               if ((choice >= 2) && (choice <= 7))
                  inputOK = true;
            }
            catch
            {
               // Ignore failed conversions, just continue prompting.
            }
         } while (inputOK == false);
         // Initialize array of Player objects.
         Player[] players = new Player[choice];
         // Get player names.
         for (int p = 0; p < players.Length; p++)
         {
            WriteLine($"Player {p + 1}, enter your name:");
            string playerName = ReadLine();
            players[p] = new Player(playerName);
         }
         // Start game.
         Game newGame = new Game();
         newGame.SetPlayers(players);
         int whoWon = newGame.PlayGame();
         // Display winning player.
         WriteLine($"{players[whoWon].Name} has won the game!");
         ReadKey();
      }

Now you come to PlayGame(), the main body of the application. Space limitations preclude us from providing a lot of detail about this method, but the code is commented to make it more comprehensible. None of the code is complicated; there's just quite a bit of it.

Play proceeds with each player viewing his or her cards and an upturned card on the table. They can either pick up this card or draw a new one from the deck. After drawing a card, each player must discard one, replacing the card on the table with another one if it has been picked up, or placing the discarded card on top of the one on the table (also adding the discarded card to the discardedCards collection).

As you consider this code, bear in mind how the Card objects are manipulated. The reason why these objects are defined as reference types, rather than value types (using a struct), should now be clear. A given Card object can appear to exist in several places at once because references can be held by the Deck object, the hand fields of the Player objects, the discardedCards collection, and the playCard object (the card currently on the table). This makes it easy to keep track of the cards and is used in particular in the code that draws a new card from the deck. The card is accepted only if it isn't in any player's hand or in the discardedCards collection.

The code is as follows:

      public int PlayGame()
      {
         // Only play if players exist.
         if (players == null)
            return -1;
         // Deal initial hands.
         DealHands();
         // Initialize game vars, including an initial card to place on the
         // table: playCard.
         bool GameWon = false;
         int currentPlayer;
         Card playCard = playDeck.GetCard(currentCard++);
         discardedCards.Add(playCard);
         // Main game loop, continues until GameWon == true.
         do
         {
            // Loop through players in each game round.
            for (currentPlayer = 0; currentPlayer < players.Length;
                 currentPlayer++)
            {
               //Write out current player, player hand, and the card on the
               // table.
               WriteLine($"{players[currentPlayer].Name}'s turn.");
               WriteLine("Current hand:");
               foreach (Card card in players[currentPlayer].PlayHand)
               {
                  WriteLine(card);
               }
               WriteLine($"Card in play: {playCard}");
               // Prompt player to pick up card on table or draw a new one.
               bool inputOK = false;
               do
               {
                  WriteLine("Press T to take card in play or D to draw:");
                  string input = ReadLine();
                  if (input.ToLower() == "t")
                  {
                     // Add card from table to player hand.
                     WriteLine("Drawn: {playCard}");
                     // Remove from discarded cards if possible (if deck
                     // is reshuffled it won't be there any more)
                     if (discardedCards.Contains(playCard))
                     {
                        discardedCards.Remove(playCard);
                     }
                     players[currentPlayer].PlayHand.Add(playCard);
                     inputOK = true;
                  }
                  if (input.ToLower() == "d")
                  {
                     // Add new card from deck to player hand.
                     Card newCard;
                     // Only add card if it isn't already in a player hand
                     // or in the discard pile
                     bool cardIsAvailable;
                     do
                     {
                        newCard = playDeck.GetCard(currentCard++);
                        // Check if card is in discard pile
                        cardIsAvailable = !discardedCards.Contains(newCard);
                        if (cardIsAvailable)
                        {
                           // Loop through all player hands to see if newCard
                           // is already in a hand.
                           foreach (Player testPlayer in players)
                           {
                              if (testPlayer.PlayHand.Contains(newCard))
                              {
                                 cardIsAvailable = false;
                                 break;
                              }
                           }
                        }
                     } while (!cardIsAvailable);
                     // Add the card found to player hand.
                     WriteLine($"Drawn: {newCard}");
                     players[currentPlayer].PlayHand.Add(newCard);
                     inputOK = true;
                  }
               } while (inputOK == false);
               // Display new hand with cards numbered.
               WriteLine("New hand:");
               for (int i = 0; i < players[currentPlayer].PlayHand.Count; i++)
               {
                  WriteLine($"{i + 1}: " +
                            $"{ players[currentPlayer].PlayHand[i]}");
               }
               // Prompt player for a card to discard.
               inputOK = false;
               int choice = -1;
               do
               {
                  WriteLine("Choose card to discard:");
                  string input = ReadLine();
                  try
                  {
                     // Attempt to convert input into a valid card number.
                     choice = Convert.ToInt32(input);
                     if ((choice > 0) && (choice <= 8))
                        inputOK = true;
                  }
                  catch
                  {
                     // Ignore failed conversions, just continue prompting.
                  }
               } while (inputOK == false);
               // Place reference to removed card in playCard (place the card
               // on the table), then remove card from player hand and add
               // to discarded card pile.
               playCard = players[currentPlayer].PlayHand[choice - 1];
               players[currentPlayer].PlayHand.RemoveAt(choice - 1);
               discardedCards.Add(playCard);
               WriteLine($»Discarding: {playCard}»);
               // Space out text for players
               WriteLine();
               // Check to see if player has won the game, and exit the player
               // loop if so.
               GameWon = players[currentPlayer].HasWon();
               if (GameWon == true)
                  break;
            }
         } while (GameWon == false);
         // End game, noting the winning player.
         return currentPlayer;
      }

Figure 13.8 shows a game in progress.

Command prompt window displaying texts from a game of cards in progress.

Figure 13.8

As a final exercise, have a close look at the code in Player.HasWon(). Can you think of a way that you could make this code more efficient, perhaps without having to examine every card in the player's hand every time this method is called?

Attributes

This section takes a brief look at a useful way to provide additional information to code that consumes types that you create: attributes. Attributes give you a way to mark sections of code with information that can be read externally and used in any number of ways to affect how your types are used. This is often referred to as decorating the code. You can find the code for this section in CustomAttributesProgram.cs in this chapter's online download.

For example, let's say you create a class with a really simple method. In fact, it's so simple that you really aren't that interested in stepping through it. Unfortunately — and to your considerable annoyance — you keep doing precisely that as you debug the code in your application. In this situation, it's possible to add an attribute to the method that tells Visual Studio not to step into the code when you debug it; instead, Visual Studio should step through it and on to the next statement. The code for this is as follows:

[DebuggerStepThrough]
public void DullMethod() { … }

The attribute in this code is [DebuggerStepThrough]. All attributes are added in this way, by enclosing the name of the attribute in square brackets just before the target to which they apply. You can add multiple attributes to a single target either by separating them with commas or by enclosing each one in square brackets.

The attribute used in the preceding code is actually implemented in a class called DebuggerStepThroughAttribute, and is found in the System.Diagnostics namespace, so you need a using statement for that namespace if you want to use this attribute. You can refer to this attribute either by its full name or, as in the code you saw, with an abbreviated name that doesn't include the suffix Attribute.

When you add an attribute in this way, the compiler creates an instance of the attribute class and associates it with the class method. Some attributes are customizable through constructor parameters or properties, and these can be specified when you add the attribute, for example:

[DoesInterestingThings(1000, WhatDoesItDo = "voodoo")]
public class DecoratedClass {}

This attribute is passing a value of 1000 to the constructor of DoesInterestingThingsAttribute and setting the value of a property called WhatDoesItDo to the string "voodoo".

Reading Attributes

In order to read attribute values, you have to use a technique called reflection. This is a fairly advanced technique that allows you to dynamically inspect type information at runtime, even to the point where you can create objects and call methods without knowing what those objects are. This book doesn't cover this technique in detail, but you do need to know some basics in order to use attributes.

Essentially, reflection involves using information stored in Type objects (which you've seen in several places in this book) along with types in the System.Reflection namespace to work with type information. You've already seen a quick way to get type information from a class with the typeof operator, and from an object instance using the GetType() method. Using reflection you can proceed to interrogate member information from the Type object. You can then obtain attribute information from the class or its various members.

The simplest way to do this — and the only way you'll see in this book — is to use the Type.GetCustomAttributes() method. This method takes up to two parameters and returns an array of object instances, each of which is an attribute instance. First, you can optionally pass the type or types of attributes you are interested in (any other attributes will be ignored). If you omit this parameter, then all attributes will be returned. Second, you must pass a Boolean value indicating whether to look just at the class or at the class and all classes that the class derives from.

For example, the following code would list the attributes of a class called DecoratedClass:

Type classType = typeof(DecoratedClass);
object[] customAttributes = classType.GetCustomAttributes(true);
foreach (object customAttribute in customAttributes)
{
   WriteLine($"Attribute of type {customAttribute} found.");
}

Once you have found attributes in this way, you can take whatever action is appropriate for the attribute. This is exactly what Visual Studio does when it encounters the DebuggerStepThroughAttribute attribute discussed earlier.

Creating Attributes

You can create your own attributes simply by deriving from the System.Attribute class. Sometimes, you don't need to do anything else, as no additional information is required if your code is interested only in the presence or absence of your attribute. However, you can supply nondefault constructors and/or writeable properties if you want the attribute to be customizable.

You also need to decide two things about your attribute: what type of target it can be applied to (class, property, and so on) and whether it can be applied more than once to the same target. You specify this information through an attribute that you apply to your attribute (this has a certain Zen feeling of correctness to it!) called AttributeUsageAttribute. This attribute has a constructor parameter of type AttributeTargets, which is an enum that allows you to combine its values with the | operator. It also has a Boolean property called AllowMultiple that specifies whether the attribute can be applied more than once.

For example, the following code specifies an attribute that can be applied (once) to a class or property:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = false)]
class DoesInterestingThingsAttribute : Attribute
{
   public DoesInterestingThingsAttribute(int howManyTimes)
   {
      HowManyTimes = howManyTimes;
   }
   public string WhatDoesItDo { get; set; }
   public int HowManyTimes { get; private set; }
}

This attribute, DoesInterestingThingsAttribute, can be used as in the earlier code snippet:

[DoesInterestingThings(1000, WhatDoesItDo = "voodoo")]
public class DecoratedClass {}

And by modifying the code in the previous section, you can gain access to the properties of the attribute:

Type classType = typeof(DecoratedClass);
object[] customAttributes = classType.GetCustomAttributes(true);
foreach (object customAttribute in customAttributes)
{
   WriteLine($"Attribute of type {customAttribute} found.");
   DoesInterestingThingsAttribute interestingAttribute =
      customAttribute as DoesInterestingThingsAttribute;
   if (interestingAttribute != null)
   {
      WriteLine($"This class does {interestingAttribute.WhatDoesItDo} x " +
                $" {interestingAttribute.HowManyTimes}!");
   }
}

Putting everything in this section together and using this code would give you the result shown in Figure 13.9.

Command prompt window displaying text Attribute of type CustomAttributes.DoesInterestingThingsAttribute found. This class does voodoo x 1000!.

Figure 13.9

Attributes can be extremely useful and crop up all over .NET applications — and WPF and Windows Store applications in particular. You will encounter them repeatedly throughout the remainder of this book.

Initializers

Up to now you have learned to instantiate and initialize objects in various ways. Invariably, that has required you either to add code to class definitions to enable initialization or to instantiate and initialize objects with separate statements. You have also learned how to create collection classes of various types, including generic collection classes. Again, you might have noticed that there was no easy way to combine the creation of a collection with adding items to the collection.

Object initializers provide a way to simplify your code by enabling you to combine instantiation and initialization of objects. Collection initializers give you a simple, elegant syntax to create and populate collections in a single step. This section explains how to use both of these features.

Object Initializers

Consider the following simple class definition:

public class Curry
{
   public string MainIngredient { get; set; }
   public string Style { get; set; }
   public int Spiciness { get; set; }
}

This class has three properties that are defined using the automatic property syntax shown in Chapter 10. If you want to instantiate and initialize an object instance of this class, you must execute several statements:

Curry tastyCurry = new Curry();
tastyCurry.MainIngredient = "panir tikka";
tastyCurry.Style = "jalfrezi";
tastyCurry.Spiciness = 8;

This code uses the default, parameter-less constructor that is supplied by the C# compiler if you don't include a constructor in your class definition. To simplify this initialization, you can supply an appropriate nondefault constructor:

public class Curry
{
   public Curry(string mainIngredient, string style,
                int spiciness)
   {
      MainIngredient = mainIngredient;
      Style = style;
      Spiciness = spiciness;
   }
   …
}

That enables you to write code that combines instantiation with initialization:

Curry tastyCurry = new Curry("panir tikka", "jalfrezi", 8);

This works fine, although it forces code that uses this class to use this constructor, which would prevent the previous code, which used a parameter-less constructor, from working. Often, particularly when classes must be serializable, it is necessary to provide a parameter-less constructor:

public class Curry
{
   public Curry() {}
   …
}

Now you have a situation where you can instantiate and initialize the Curry class any way you like. However, you have added several lines of code to the initial class definition that don't do anything much other than provide the basic plumbing required for this flexibility.

Enter object initializers, which are a way to instantiate and initialize objects without having to add code (such as the constructors detailed here) to a class. When you instantiate an object, you supply values for publicly accessible properties or fields using a name/value pair for each property you want to initialize. The syntax for this is as follows:

<ClassName> <variableName> = new <ClassName>
{
   <propertyOrField1> = <value1>,
   <propertyOrField2> = <value2>,
   …
   <propertyOrFieldN> = <valueN>
};

For example, you could rewrite the code shown earlier, which instantiates and initializes an object of type Curry, as follows:

Curry tastyCurry = new Curry
{
   MainIngredient = "panir tikka",
   Style = "jalfrezi",
   Spiciness = 8
};

Often you can put code like that on a single line without seriously degrading readability.

When you use an object initializer, you don't have to explicitly call a constructor of the class. If you omit the constructor parentheses (as in the previous code), the default parameter-less constructor is called automatically. This happens before any parameter values are set by the initializer, which enables you to provide default values for parameters in the default constructor if desired. Alternatively, you can call a specific constructor. Again, this constructor is called first, so any initialization of public properties that takes place in the constructor might be overridden by values that you provide in the initializer. You must have access to the constructor that you use (or the default one if you aren't explicit) in order for object initializers to work.

If one of the properties you want to initialize with an object initializer is more complex than the simple types used in this example, then you might find yourself using a nested object initializer. That simply means using the exact same syntax you've already seen:

Curry tastyCurry = new Curry
{
   MainIngredient = "panir tikka",
   Style = "jalfrezi",
   Spiciness = 8,
   Origin = new Restaurant
   {
      Name = "King's Balti",
      Location = "York Road",
      Rating = 5
   }
};

Here, a property called Origin of type Restaurant (not shown here) is initialized. The code initializes three properties of the Origin property — Name, Location, and Rating — with values of type string, string, and int, respectively. This initialization uses a nested object initializer.

Note that object initializers are not a replacement for nondefault constructors. The fact that you can use object initializers to set property and field values when you instantiate an object does not mean that you will always know what state needs initializing. With constructors you can specify exactly which values are required for an object to function and then execute code in response to those values immediately.

Also, in the previous example there is another (admittedly quite subtle) difference between using a nested object initializer and using constructors. This difference is the order in which objects get created. With a nested initializer, the top level object (Curry) gets created first. Next, the nested object (Restaurant) is created and assigned to the property Origin. If you used a constructor, you would reverse this construction order and pass the Restaurant instance to the constructor of Curry. In this simple example, there is no practical difference, but in some circumstances this might be significant.

Collection Initializers

Chapter 5 described how arrays can be initialized with values using the following syntax:

int[] myIntArray = new int[5] { 5, 9, 10, 2, 99 };

This is a quick and easy way to combine the instantiation and initialization of an array. Collection initializers simply extend this syntax to collections:

List<int> myIntCollection = new List<int> { 5, 9, 10, 2, 99 };

By combining object and collection initializers, it is possible to configure collections with simple and elegant code. Rather than code like this:

List<Curry> curries = new List<Curry>();
curries.Add(new Curry("Chicken", "Pathia", 6));
curries.Add(new Curry("Vegetable", "Korma", 3));
curries.Add(new Curry("Prawn", "Vindaloo", 9));

You can use the following:

List<Curry> moreCurries = new List<Curry>
{
   new Curry
   {
      MainIngredient = "Chicken",
      Style = "Pathia",
      Spiciness = 6
   },
   new Curry
   {
      MainIngredient = "Vegetable",
      Style = "Korma",
      Spiciness = 3
   },
   new Curry
   {
      MainIngredient = "Prawn",
      Style = "Vindaloo",
      Spiciness = 9
   }
};

This works very well for types that are primarily used for data representation, and as such, collection initializers are a great accompaniment to the LINQ technology described later in the book.

The following Try It Out illustrates how you can use object and collection initializers.

Type Inference

Earlier in this book you saw how C# is a strongly typed language, which means that every variable has a fixed type and can be used only in code that takes that type into account. In every code example you've seen so far, you have declared variables in one of two ways:

<type> <varName>;
<type> <varName> = <value>;

The following code shows at a glance what type of variable <varName> is:

int myInt = 5;
WriteLine(myInt);

You can also see that the IDE is aware of the variable type simply by hovering the mouse pointer over the variable identifier, as shown in Figure 13.12.

Snipped image displaying the codes, int myInt = 5; and WriteLine(myInt); and a box at the bottom right displaying icon labeled (local variable) int myInt.

Figure 13.12

C# 3 introduced the new keyword var, which you can use as an alternative for type in the preceding code:

var <varName> = <value>;

In this code, the variable <varName> is implicitly typed to the type of <value>. Note that there is no type called var. In the code:

var myVar = 5;

myVar is a variable of type int, not of type var. Again, as shown in Figure 13.13, the IDE is aware of this.

Snipped image displaying the codes, var myInt = 5; and WriteLine(myInt); and a box at the bottom right displaying icon labeled (local variable) int myInt.

Figure 13.13

This is an extremely important point. When you use var you are not declaring a variable with no type, or even a type that can change. If that were the case, C# would no longer be a strongly typed language. All you are doing is relying on the compiler to determine the type of the variable.

If the compiler is unable to determine the type of variable declared using var, then your code will not compile. Therefore, you can't declare a variable using var without initializing the variable at the same time. If you do this, there is no value that the compiler can use to determine the type of the variable. The following code, therefore, will not compile:

var myVar;

The var keyword can also be used to infer the type of an array through the array initializer:

var myArray = new[] { 4, 5, 2 };

In this code, the type myArray is implicitly int[]. When you implicitly type an array in this way, the array elements used in the initializer must be one of the following:

  • All the same type
  • All the same reference type or null
  • All elements that can be implicitly converted to a single type

If the last of these rules is applied, then the type that elements can be converted to is referred to as the best type for the array elements. If there is any ambiguity as to what this best type might be — that is, if there are two or more types that all the elements can be implicitly converted to — your code will not compile. Instead, you receive the error indicating that no best type is available, as in the following code:

var myArray = new[] { 4, "not an int", 2 };

Note also that numeric values are never interpreted as nullable types, so the following code will not compile:

var myArray = new[] { 4, null, 2 };

You can, however, use a standard array initializer to make this work:

var myArray = new int?[] { 4, null, 2 };

A final point: The identifier var is not a forbidden identifier to use for a class name. This means, for example, that if your code has a class called var in scope (in the same namespace or in a referenced namespace), then you cannot use implicit typing with the var keyword.

In itself, type inference is not particularly useful because in the code you've seen in this section it only serves to complicate things. Using var makes it more difficult to see at a glance the type of a given variable. However, as you will see later in this chapter, the concept of inferred types is important because it underlies other techniques. The next subject, anonymous types, is one for which inferred types are essential.

Anonymous Types

After programming for a while you might find, especially in database applications, that you spend a lot of time creating simple, dull classes for data representation. It is not unusual to have families of classes that do absolutely nothing other than expose properties. The Curry class shown earlier in this chapter is a perfect example:

public class Curry
{
   public string MainIngredient { get; set; }
   public string Style { get; set; }
   public int Spiciness { get; set; }
}

This class doesn't do anything — it merely stores structured data. In database or spreadsheet terms, you could think of this class as representing a row in a table. A collection class that was capable of holding instances of this class would be a representation of multiple rows in a table or spreadsheet.

This is a perfectly acceptable use of classes, but writing the code for these classes can become monotonous, and any modifications to the underlying data schema requires you to add, remove, or modify the code that defines the classes.

Anonymous types are a way to simplify this programming model. The idea behind anonymous types is that rather than define these simple data storage types, you can instead use the C# compiler to automatically create types based on the data that you want to store in them.

The preceding Curry type can be instantiated as follows:

Curry curry = new Curry
{
   MainIngredient = "Lamb",
   Style = "Dhansak",
   Spiciness = 5
};

Alternatively, you could use an anonymous type, as in the following code:

var curry = new
{
   MainIngredient = "Lamb",
   Style = "Dhansak",
   Spiciness = 5
};

There are two differences here. First, the var keyword is used. That's because anonymous types do not have an identifier that you can use. Internally they do have an identifier, as you will see in a moment, but it is not available to you in your code. Second, no type name is specified after the new keyword. That's how the compiler knows you want to use an anonymous type.

The IDE detects the anonymous type definition and updates IntelliSense accordingly. With the preceding declaration, you can see the anonymous type, as shown in Figure 13.14.

Snipped image of variable curry code, with (local variable) 'a curry and Anonymous types: 'a is new { string MainIngredient, string style, int spiciness }.

Figure 13.14

Here, internally, the type of the variable curry is 'a. Obviously, you can't use this type in your code — it's not even a legal identifier name. The ' is simply the symbol used to denote an anonymous type in IntelliSense. IntelliSense also enables you to inspect the members of the anonymous type, as shown in Figure 13.15.

Snipped image of variable curry code, with string 'a.style and Anonymous types: 'a is new { string mainingredient, string style, int spiciness }.

Figure 13.15

Note that the properties shown here are defined as read-only properties. This means that if you want to be able to change the values of properties in your data storage objects, you cannot use anonymous types.

The other members of anonymous types are implemented, as shown in the following Try It Out.

Dynamic Lookup

The var keyword, as described earlier, is not in itself a type, and so doesn't break the “strongly typed” methodology of C#. From C# 4 onward, though, things have become a little less fixed. C# 4 introduced the concept of dynamic variables, which, as their name suggests, are variables that do not have a fixed type.

The main motivation for this is that there are many situations where you will want to use C# to manipulate objects created by another language. This includes interoperability with older technologies such as the Component Object Model (COM), as well as dealing with dynamic languages such as JavaScript, Python, and Ruby. Without going into too much implementation detail, using C# to access methods and properties of objects created by these languages has, in the past, involved awkward syntax. For example, say you had code that obtained an object from JavaScript with a method called Add() that added two numbers. Without dynamic lookup, your code to call this method might look something like the following:

ScriptObject jsObj = SomeMethodThatGetsTheObject();
int sum = Convert.ToInt32(jsObj.Invoke("Add", 2, 3));

The ScriptObject type (not covered in depth here) provides a way to access a JavaScript object, but even this is unable to give you the capability to do the following:

int sum = jsObj.Add(2, 3);

Dynamic lookup changes everything — enabling you to write code just like the preceding. However, as you will see in the following sections, this power comes at a price.

Another situation in which dynamic lookup can assist you is when you are dealing with a C# object whose type you do not know. This might sound like an odd situation, but it happens more often than you might think. It is also an important capability when writing generic code that can deal with whatever input it receives. The “old” way to deal with this situation is called reflection, which involves using type information to access types and members. The syntax for using reflection to access type members such as methods is quite similar to the syntax used to access the JavaScript object, as shown in the preceding code. In other words, it's messy.

Under the hood, dynamic lookup is supported by the Dynamic Language Runtime (DLR). This is part of .NET 4.5, just as the CLR is. An exact description of the DLR and how it makes interoperability easier is beyond the scope of this book; here you're more interested in how to use it in C#.

The dynamic Type

C# 4 introduced the dynamic keyword, which you can use to define variables, as in this example:

dynamic myDynamicVar;

Unlike the var keyword introduced earlier, there really is a dynamic type, so there is no need to initialize the value of myDynamicVar when it is declared.

Once you have a dynamic variable, you can proceed to access its members (the code to obtain a value for the variable is not shown here):

myDynamicVar.DoSomething("With this!");

Regardless of the value that myDynamicVar contains, this code will compile. However, if the requested member does not exist, you will get an exception when this code is executed, of type RuntimeBinderException.

In effect, what you are doing with code like this is providing a “recipe” that should be applied at runtime. The value of myDynamicVar will be examined, and a method called DoSomething() with a single string parameter will be located and called at the point where it is required.

This is best illustrated with an example.

The lesson to be learned here is that dynamic types are very powerful, but there's a warning to learn too. These sorts of exceptions are entirely avoidable if you use strong typing instead of dynamic typing. For most C# code that you write, avoid the dynamic keyword. However, if a situation arises where you need to use it, use it and love it — and spare a thought for those poor programmers of the past who didn't have this powerful tool at their disposal.

Advanced Method Parameters

C# 4 extended what is possible when defining and using method parameters. This is primarily in response to a specific problem that arises when using interfaces defined externally, such as the Microsoft Office programming model. Here, certain methods expose a vast number of parameters, many of which are not required for every call. In the past, this has meant that a way to specify missing parameters has been necessary, or that a lot of nulls appear in code:

RemoteCall(var1, var2, null, null, null, null, null);

In this code it is not at all obvious what the null values refer to, or why they have been omitted.

Perhaps, in an ideal world, there would be multiple overloads of this RemoteCall() method, including one that only required two parameters as follows:

RemoteCall(var1, var2);

However, this would require many more methods with alternative combinations of parameters, which in itself would cause more problems (more code to maintain, increased code complexity, and so on).

Languages such as Visual Basic have dealt with this situation in a different way, by allowing named and optional parameters. From version 4, this became possible in C#, demonstrating one way in which the evolution of all .NET languages is converging.

In the following sections you will see how to use these parameter types.

Optional Parameters

Often when you call a method, you pass in the same value for a particular parameter. This can be a Boolean value, for example, which might control a nonessential part of the method's operation. To be more specific, consider the following method definition:

public List<string> GetWords(string sentence, bool capitalizeWords)
{
   …
}

Regardless of the value passed into the capitalizeWords parameter, this method will return a list of string values, each of which is a word from the input sentence. Depending on how this method was used, you might occasionally want to capitalize the list of words returned (perhaps you are formatting a heading such as the one for this section, “Optional Parameters”). In most cases, though, you might not want to do this, so most calls would be as follows:

List<string> words = GetWords(sentence, false);

To make this the “default” behavior, you might declare a second method as follows:

public List<string> GetWords(string sentence) => GetWords(sentence, false);

This method calls into the second method, passing a value of false for capitalizeWords.

There is nothing wrong with doing this, but you can probably imagine how complicated this would become in a situation where many more parameters were used.

An alternative is to make the capitalizeWords parameter an optional parameter. This involves defining the parameter as optional in the method definition by providing a default value that will be used if none is supplied, as follows:

public List<string> GetWords(string sentence, bool capitalizeWords = false)
{
   …
}

If you were to define a method in this way, then you could supply either one or two parameters, where the second parameter is required only if you want capitalizeWords to be true.

Optional Parameter Values

As described in the previous section, a method definition defines an optional parameter with syntax as follows:

<parameterType> <parameterName> = <defaultValue>

There are restrictions on what you can use for the <defaultValue> default value. Default values must be literal values, constant values, or default value type values. The following, therefore, will not compile:

public bool CapitalizationDefault;
public List<string> GetWords(string sentence,
   bool capitalizeWords = CapitalizationDefault)
{
   …
}

In order to make this work, the CapitalizationDefault value must be defined as a constant:

public const bool CapitalizationDefault = false;

Whether it makes sense to do this depends on the situation; in most cases you will probably be better off providing a literal value as in the previous section.

The OptionalAttribute Attribute

As an alternative to the syntax described in the previous sections, you can define optional parameters using the OptionalAttibute attribute as follows:

[Optional] <parameterType> <parameterName>

This attribute is found in the System.Runtime.InteropServices namespace. Note that if you use this syntax there is no way to provide a default value for the parameter.

Optional Parameter Order

When you use optional values, they must appear at the end of the list of parameters for a method. No parameters without default values can appear after any parameters with default values.

The following code, therefore, is illegal:

public List<string> GetWords(bool capitalizeWords = false, string sentence)
{
   …
}

Here, sentence is a required parameter, and must therefore appear before the optional capitalizedWords parameter.

Named Parameters

When you use optional parameters, you might find yourself in a situation where a particular method has several optional parameters. It's not beyond the realm of the imagination, then, to conceive of a situation where you want to pass a value to, say, only the third optional parameter. With just the syntax from the previous section there is no way to do this without supplying values for the first and second optional parameters.

C# 4 also introduced named parameters that enable you to specify whichever parameters you want. This doesn't require you to do anything in particular in your method definition; it is a technique that you use when you are calling a method. The syntax is as follows:

MyMethod(
   <param1Name>: <param1Value>,
   …
   <paramNName>: <paramNValue>);

The names of parameters are the names of the variables used in the method definition.

You can specify any number of parameters you like in this way, as long as the named parameters exist, and you can do so in any order. Named parameters can be optional as well.

You can, if you want, use named parameters for only some of the parameters in a method call. This is particularly useful when you have several optional parameters in a method signature, but some required parameters. You might specify the required parameters first, then finish off with named optional parameters. For example:

MyMethod(
   requiredParameter1Value,
   optionalParameter5: optionalParameter5Value);

If you mix named and positional parameters, though, note that you must include all positional parameters first, before the named parameters. However, you can use a different order if you prefer as long as you use named parameters throughout, as in this example:

MyMethod(
   optionalParameter5: optionalParameter5Value,
   requiredParameter1: requiredParameter1Value);

If you do this you must include values for all required parameters.

In the following Try It Out, you will see how you can use named and optional parameters.

This is a very useful tooltip, as it shows not only the names of available parameters, but also the default values for optional parameters, so you can tell at a glance if you need to override a particular default.

Lambda Expressions

Lambda expressions are a construct introduced in C# 3 that you can use to simplify certain aspects of C# programming, in particular when combined with LINQ. They can be difficult to grasp at first, mainly because they are so flexible in their usage. Lambda expressions are extremely useful when combined with other C# language features, such as anonymous methods. Without looking at LINQ, a subject left until later in the book, anonymous methods are the best entry point for examining this subject. Start with a quick refresher.

Anonymous Methods Recap

Previously in this chapter you learned about anonymous methods — methods that you supply inline, where a delegate type variable would otherwise be required. When you add an event handler to an event, the sequence of events is as follows:

  1. Define an event handler method whose return type and parameters match those of the delegate required for the event to which you want to subscribe.
  2. Declare a variable of the delegate type used for the event.
  3. Initialize the delegate variable to an instance of the delegate type that refers to the event handler method.
  4. Add the delegate variable to the list of subscribers for the event.

In practice, things are a bit simpler than this because you typically won't bother with a variable to store the delegate — you will just use an instance of the delegate when you subscribe to the event.

This was the case when you previously used the following code:

Timer myTimer = new Timer(100);
myTimer.Elapsed += new ElapsedEventHandler(WriteChar);

This code subscribes to the Elapsed event of a Timer object. This event uses the ElapsedEventHandler delegate type, which is instantiated using a method identifier, WriteChar. The result here is that when the Timer raises the Elapsed event, the WriteChar() method is called. The parameters passed to WriteChar() depend on the parameter types defined by the ElapsedEventHandler delegate and the values passed by the code in Timer that raises the event.

In fact, the C# compiler can achieve the same result with even less code through method group syntax:

myTimer.Elapsed += WriteChar;

The C# compiler knows the delegate type required by the Elapsed event, so it can fill in the blanks. However, you should use this syntax with care because it can make it harder to read your code and know exactly what is happening. When you use an anonymous method, the sequence of events shown earlier is reduced to a single step:

  1. Use an inline, anonymous method that matches the return type and the parameters of the delegate required by an event to subscribe to that event.

The inline, anonymous method is defined by using the delegate keyword:

myTimer.Elapsed +=
   delegate(object source, ElapsedEventArgs e)
   {
      WriteLine("Event handler called after {0} milliseconds.",
         (source as Timer).Interval);
   };

This code works just as well as using the event handler separately. The main difference is that the anonymous method used here is effectively hidden from the rest of your code. You cannot, for example, reuse this event handler elsewhere in your application. In addition, the syntax used here is, for want of a better description, a little clunky. The delegate keyword is immediately confusing because it is effectively being overloaded — you use it both for anonymous methods and for defining delegate types.

Lambda Expressions for Anonymous Methods

This brings you to lambda expressions. Lambda expressions are a way to simplify the syntax of anonymous methods. In fact, they are more than that, but this section will keep things simple for now. Using a lambda expression, you can rewrite the code at the end of the previous section as follows:

myTimer.Elapsed += (source, e) => WriteLine("Event handler called after " +
                      $"{(source as Timer).Interval} milliseconds.");

At first glance this looks…well, a little baffling (unless you are familiar with so-called functional programming languages such as Lisp or Haskell, that is). However, if you look closer you can see, or at least infer, how this works and how it relates to the anonymous method that it replaces. The lambda expression is made up of three parts:

  • A list of (untyped) parameters in parentheses
  • The => operator
  • A C# statement

The types of the parameters are inferred from the context, using the same logic shown in the section “Anonymous Types” earlier in this chapter. The => operator simply separates the parameter list from the expression body. The expression body is executed when the lambda expression is called.

The compiler takes this lambda expression and creates an anonymous method that works exactly the same way as the anonymous method in the previous section. In fact, it will be compiled into the same or similar Common Intermediate Language (CIL) code.

The following Try It Out clarifies what occurs in lambda expressions.

image What You Learned in This Chapter

Topic Key Concepts
Namespace qualification To avoid ambiguity in namespace qualification, you can use the : : operator to force the compiler to use aliases that you have created. You can also use the global namespace as an alias for the top-level namespace.
Custom exceptions You can create your own exception classes by deriving from the root Exception class. This is helpful because it gives you greater control over catching specific exceptions, and allows you to customize the data that is contained in an exception in order to deal with it effectively.
Event handling Many classes expose events that are raised when certain triggers occur in their code. You can write handlers for these events to execute code at the point where they are raised. This two-way communication is a great mechanism for responsive code, and prevents you from having to write what would otherwise be complex, convoluted code that might poll an object for changes.
Event definitions You can define your own event types, which involves creating a named event and a delegate type for any handlers for the event. You can use the standard delegate type with no return type and custom event arguments that derive from System.EventArgs to allow for multipurpose event handlers. You can also use the EventHandler and EventHandler<T> delegate types to define events with simpler code.
Anonymous methods Often, to make your code more readable, you can use an anonymous method instead of a full event handler method. This means defining the code to execute when an event is raised in-line at the point where you add the event handler. You achieve this with the delegate keyword.
Attributes Occasionally, either because the framework you are using demands it or because you choose to, you will make use of attributes in your code. You can add attributes to classes, methods and other members using [AttributeName] syntax, and you can create your own attributes by deriving from System.Attribute. You can read attribute values through reflection.
Initializers You can use initializers to initialize an object or collection at the same time as creating it. Both types of initializers consist of a block of code surrounded by curly brackets. Object initializers allow you to set property values by providing a comma-separated list of property name/value pairs. Collection initializers simply require a comma-separated list of values. When you use an object initializer, you can also use a nondefault constructor.
Type inference The var keyword allows you to omit the type of a variable when you declare it. However, this is possible only if the type can be determined at compile time. Using var does not break the strong typing methodology of C# as a variable declared with var has one and only one possible type.
Anonymous types For many simple types used to structure data storage, defining a type is not necessary. Instead, you can use an anonymous type, whose members are inferred from usage. You define an anonymous type with object initializer syntax, and every property you set is defined as a read-only property.
Dynamic lookup Use the dynamic keyword to define a dynamic type variable that can hold any value. You can then access members of the contained value with normal property or method syntax, and these are only checked at runtime. If, at runtime, you attempt to access a nonexistent member, an exception is thrown. This dynamic typing greatly simplifies the syntax required to access non-.NET types, or .NET types whose type information is not available at compile time. However, dynamic types must be used with caution as you lose compile time code checking. You can control the behavior of dynamic lookup by implementing the IDynamicMetaObjectProvider interface.
Optional method parameters Often, you can define a method with lots of parameters, many of which are only rarely used. Instead of forcing client code to specify values for rarely used parameters, you might provide multiple method overloads. Alternatively, you can define these parameters as optional (and provide default values for parameters that are not specified). Client code that calls your method can then specify only as many parameters as are required.
Named method parameters Client code can specify method parameter values by position or by name (or a mix of the two where positional parameters are specified first). Named parameters can be specified in any order. This is particularly useful when combined with optional parameters.
Lambda expressions Lambda expressions are essentially a shorthand way of defining anonymous methods, although they have additional capabilities such as implicit typing. You define a lambda expression with a comma-separated list of parameters (or empty parentheses for no parameters), the => operator, and an expression. The expression can be a block of code enclosed in curly brackets. Lambda expressions with up to eight parameters and an optional return type can be represented with the Action, Action<>, and Func<> delegate types. Many LINQ extension methods that can be used with collections use lambda expression parameters.
..................Content has been hidden....................

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