Chapter 1. Classes and Generics
1.0 Introduction
The recipes in this chapter cover the foundation of the C# language. Topics include classes and structures, how they are used, how they are different, and when you would use one over the other. Building on this, we will construct classes that have inherent functionality such as being sortable, searchable, disposable, and cloneable. In addition, we will dive into topics such as union types, field initialization, lambdas, partial methods, single and multicast delegates, closures, functors, and more. This chapter also contains a recipe on parsing command-line parameters, which is always a favorite.
Before diving into the recipes, let’s review some key information about the object-oriented capabilities of classes, structures, and generics. Classes are much more flexible than structures. Like classes, structures can implement interfaces, but unlike classes, they cannot inherit from a class or a structure. This limitation precludes creating structure hierarchies, as you can do with classes. Polymorphism, as implemented through an abstract base class, is also prohibited when you are using a structure, since a structure cannot inherit from another class with the exception of boxing to Object
, ValueType
, or Enum
.
Structures, like any other value type, implicitly inherit from System.ValueType
. At first glance, a structure is similar to a class, but it is actually very different. Knowing when to use a structure over a class will help you tremendously when you’re designing an application. Using a structure incorrectly can result in inefficient and hard-to-modify code.
Structures have two performance advantages over reference types. First, if a structure is allocated on the stack (i.e., it is not contained within a reference type), access to the structure and its data is somewhat faster than access to a reference type on the heap. Reference-type objects must follow their reference onto the heap in order to get at their data. However, this performance advantage pales in comparison to the second performance advantage of structures—namely, that cleaning up the memory allocated to a structure on the stack requires a simple change of the address to which the stack pointer points, which is done at the return of a method call. This call is extremely fast compared to allowing the garbage collector to automatically clean up reference types for you in the managed heap; however, the cost of the garbage collector is deferred so that it’s not immediately noticeable.
The performance of structures falls short in comparison to that of classes when they are passed by value to other methods. Because they reside on the stack, a structure and its data have to be copied to a new local variable (the method’s parameter that is used to receive the structure) when it is passed by value to a method. This copying takes more time than passing a method a single reference to an object, unless the structure is the same size as or smaller than the machine’s pointer size; thus, a structure with a size of 32 bits is just as cheap to pass as a reference (which happens to be the size of a pointer) on a 32-bit machine. Keep this in mind when choosing between a class and a structure. While creating, accessing, and destroying a class’s object may take longer, it also might not balance the performance hit when a structure is passed by value a large number of times to one or more methods. Keeping the size of the structure small minimizes the performance hit of passing it around by value.
Use a class if:
-
Its identity is important. Structures get copied implicitly when being passed by value into a method.
-
It will have a large memory footprint.
-
Its fields need initializers.
-
You need to inherit from a base class.
-
You need polymorphic behavior; that is, you need to implement an abstract base class from which you will create several similar classes that inherit from this abstract base class. (Note that polymorphism can be implemented via interfaces as well, but it is usually not a good idea to place an interface on a value type, since, if the structure is converted to the interface type, you will incur a performance penalty from the boxing operation.)
Use a structure if:
-
It must have a small memory footprint.
-
You are calling a
P/Invoke
method that requires a structure to be passed in by value. Platform Invoke, or P/Invoke for short, allows managed code to call out to an unmanaged method exposed from within a DLL. Many times, an unmanaged DLL method requires a structure to be passed in to it; using a structure is an efficient method of doing this and is the only way if the structure is being passed by value. -
You need to reduce the impact of garbage collection on application performance.
-
Its fields need to be initialized only to their default values. This value would be
zero
for numeric types,false
for Boolean types, andnull
for reference types. Note that in C# 6.0 structs can have a default constructor that can be used to initialize the struct’s fields to nondefault values. -
You do not need to inherit from a base class (other than
ValueType
, from which all structs inherit). -
You do not need polymorphic behavior.
Structures can also cause degradation in performance when they are passed to methods that require an object, such as any of the nongeneric collection types in the Framework Class Library (FCL). Passing a structure (or any simple type, for that matter) into a method requiring an object causes the structure to be boxed. Boxing is wrapping a value type in an object. This operation is time-consuming and may degrade performance.
Finally, adding generics to this mix allows you to write type-safe and efficient collection- and pattern-based code. Generics add quite a bit of programming power, but with that power comes the responsibility to use it correctly. If you are considering converting your ArrayList
, Queue
, Stack
, and Hashtable
objects to use their generic counterparts, consider reading Recipes 1.9 and 1.10. As you will read, the conversion is not always simple and easy, and there are reasons why you might not want to do this conversion at all.
1.1 Creating Union-Type Structures
Solution
Use a structure and mark it with the StructLayout
attribute (specifying the LayoutKind.Explicit
layout kind in the constructor). In addition, mark each field in the structure with the FieldOffset
attribute. The following structure defines a union in which a single signed numeric value can be stored:
using System.Runtime.InteropServices; [StructLayoutAttribute(LayoutKind.Explicit)] struct SignedNumber { [FieldOffsetAttribute(0)] public sbyte Num1; [FieldOffsetAttribute(0)] public short Num2; [FieldOffsetAttribute(0)] public int Num3; [FieldOffsetAttribute(0)] public long Num4; [FieldOffsetAttribute(0)] public float Num5; [FieldOffsetAttribute(0)] public double Num6; }
The next structure is similar to the SignedNumber
structure, except that it can contain a String
type in addition to the signed numeric value:
[StructLayoutAttribute(LayoutKind.Explicit)] struct SignedNumberWithText { [FieldOffsetAttribute(0)] public sbyte Num1; [FieldOffsetAttribute(0)] public short Num2; [FieldOffsetAttribute(0)] public int Num3; [FieldOffsetAttribute(0)] public long Num4; [FieldOffsetAttribute(0)] public float Num5; [FieldOffsetAttribute(0)] public double Num6; [FieldOffsetAttribute(16)] public string Text1; }
Discussion
Unions are structures usually found in C++ code; however, there is a way to duplicate that type of structure using a C# structure data type. A union is a structure that accepts more than one type at a specific location in memory for that structure. For example, the SignedNumber
structure is a union-type structure built using a C# structure. This structure accepts any type of signed numeric type (sbyte
, int
, long
, etc.), but it accepts this numeric type at only one location, or offset, within the structure.
Note
Since StructLayoutAttribute
can be applied to both structures and classes, you can also use a class when creating a union data type.
Notice the FieldOffsetAttribute
has the value 0
passed to its constructor. This denotes that this field will be offset by zero bytes from the beginning of the structure. This attribute is used in tandem with the StructLayoutAttribute
to manually enforce where the fields in this structure will start (that is, the offset from the beginning of this structure in memory where each field will start). The FieldOffsetAttribute
can be used only with a StructLayoutAttribute
set to LayoutKind.Explicit
. In addition, it cannot be used on static members within this structure.
Unions can become problematic, since several types are essentially laid on top of one another. The biggest problem is extracting the correct data type from a union structure. Consider what happens if you choose to store the long
numeric value long
.MaxValue
in the SignedNumber
structure. Later, you might accidentally attempt to extract a byte
data type value from this same structure. In doing so, you will get back only the first byte of the long value.
Another problem is starting fields at the correct offset. The SignedNumberWithText
union overlays numerous signed numeric data types at the zeroth offset. The last field in this structure is laid out at the 16th byte offset from the beginning of this structure in memory. If you accidentally overlay the string field Text1
on top of any of the other signed numeric data types, you will get an exception at runtime. The basic rule is that you are allowed to overlay a value type on another value type, but you cannot overlay a reference type over a value type. If the Text1
field is marked with the following attribute:
[FieldOffsetAttribute(14)]
this exception is thrown at runtime (note that the compiler does not catch this problem):
System.TypeLoadException: Could not load type 'SignedNumberWithText' from assembly 'CSharpRecipes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fe85c3941fbcc4c5' because it contains an object field at offset 14 that is incorrectly aligned or overlapped by a non-object field.
It is imperative to get the offsets correct when you’re using complex unions in C#.
See Also
The “StructLayoutAttribute Class” topic in the MSDN documentation.
1.2 Making a Type Sortable
Problem
You have a data type that will be stored as an element in a List<T>
or a SortedList<K,V>
. You would like to use the List<T>.Sort
method or the internal sorting mechanism of SortedList<K,V>
to allow custom sorting of your data types in the array. In addition, you may need to use this type in a SortedList
collection.
Solution
Example 1-1 demonstrates how to implement the IComparable<T>
interface. The Square
class shown in Example 1-1 implements this interface in such a way that the List<T>
and SortedList<K,V>
collections can sort and search for these Square
objects.
Example 1-1. Making a type sortable by implementing IComparable<T>
public class Square : IComparable<Square> { public Square(){} public Square(int height, int width) { this.Height = height; this.Width = width; } public int Height { get; set; } public int Width { get; set; } public int CompareTo(object obj) { Square square = obj as Square; if (square != null) return CompareTo(square); throw new ArgumentException( "Both objects being compared must be of type Square."); } public override string ToString()=> ($"Height: {this.Height} Width: {this.Width}"); public override bool Equals(object obj) { if (obj == null) return false; Square square = obj as Square; if(square != null) return this.Height == square.Height; return false; } public override int GetHashCode() { return this.Height.GetHashCode() | this.Width.GetHashCode(); } public static bool operator ==(Square x, Square y) => x.Equals(y); public static bool operator !=(Square x, Square y) => !(x == y); public static bool operator <(Square x, Square y) => (x.CompareTo(y) < 0); public static bool operator >(Square x, Square y) => (x.CompareTo(y) > 0); public int CompareTo(Square other) { long area1 = this.Height * this.Width; long area2 = other.Height * other.Width; if (area1 == area2) return 0; else if (area1 > area2) return 1; else if (area1 < area2) return -1; else return -1; } }
Discussion
By implementing the IComparable<T>
interface on your class (or structure), you can take advantage of the sorting routines of the List<T>
and SortedList<K,V>
classes. The algorithms for sorting are built into these classes; all you have to do is tell them how to sort your classes via the code you implement in the IComparable<T>.CompareTo
method.
When you sort a list of Square
objects by calling the List<Square>.Sort
method, the list is sorted via the IComparable<Square>
interface of the Square
objects. The Add
method of the SortedList<K,V>
class uses this interface to sort the objects as they are being added to the SortedList<K,V>
.
IComparer<T>
is designed to solve the problem of allowing objects to be sorted based on different criteria in different contexts. This interface also allows you to sort types that you did not write. If you also wanted to sort the Square
objects by height, you could create a new class called CompareHeight
, shown in Example 1-2, which would also implement the IComparer<Square>
interface.
Example 1-2. Making a type sortable by implementing IComparer
public class CompareHeight : IComparer<Square> { public int Compare(object firstSquare, object secondSquare) { Square square1 = firstSquare as Square; Square square2 = secondSquare as Square; if (square1 == null || square2 == null) throw (new ArgumentException("Both parameters must be of type Square.")); else return Compare(firstSquare,secondSquare); } #region IComparer<Square> Members public int Compare(Square x, Square y) { if (x.Height == y.Height) return 0; else if (x.Height > y.Height) return 1; else if (x.Height < y.Height) return -1; else return -1; } #endregion }
This class is then passed in to the IComparer
parameter of the Sort
routine. Now you can specify different ways to sort your Square
objects. The comparison method implemented in the comparer must be consistent and apply a total ordering so that when the comparison function declares equality for two items, it is absolutely true and not a result of one item not being greater than another or one item not being less than another.
Note
For best performance, keep the CompareTo
method short and efficient, because it will be called multiple times by the Sort
methods. For example, in sorting an array with four items, the Compare
method is called 10 times.
The TestSort
method shown in Example 1-3 demonstrates how to use the Square
and CompareHeight
classes with the List<Square>
and SortedList<int,Square>
instances.
Example 1-3. TestSort method
public static void TestSort() { List<Square> listOfSquares = new List<Square>(){ new Square(1,3), new Square(4,3), new Square(2,1), new Square(6,1)}; // Test a List<String> Console.WriteLine("List<String>"); Console.WriteLine("Original list"); foreach (Square square in listOfSquares) { Console.WriteLine(square.ToString()); } Console.WriteLine(); IComparer<Square> heightCompare = new CompareHeight(); listOfSquares.Sort(heightCompare); Console.WriteLine("Sorted list using IComparer<Square>=heightCompare"); foreach (Square square in listOfSquares) { Console.WriteLine(square.ToString()); } Console.WriteLine(); Console.WriteLine("Sorted list using IComparable<Square>"); listOfSquares.Sort(); foreach (Square square in listOfSquares) { Console.WriteLine(square.ToString()); } // Test a SORTEDLIST var sortedListOfSquares = new SortedList<int,Square>(){ { 0, new Square(1,3)}, { 2, new Square(3,3)}, { 1, new Square(2,1)}, { 3, new Square(6,1)}}; Console.WriteLine(); Console.WriteLine(); Console.WriteLine("SortedList<Square>"); foreach (KeyValuePair<int,Square> kvp in sortedListOfSquares) { Console.WriteLine ($"{kvp.Key} : {kvp.Value}"); } }
This code displays the following output:
List<String> Original list Height:1 Width:3 Height:4 Width:3 Height:2 Width:1 Height:6 Width:1 Sorted list using IComparer<Square>=heightCompare Height:1 Width:3 Height:2 Width:1 Height:4 Width:3 Height:6 Width:1 Sorted list using IComparable<Square> Height:2 Width:1 Height:1 Width:3 Height:6 Width:1 Height:4 Width:3 SortedList<Square> 0 : Height:1 Width:3 1 : Height:2 Width:1 2 : Height:3 Width:3 3 : Height:6 Width:1
See Also
Recipe 1.3, and the “IComparable<T> Interface” topic in the MSDN documentation.
1.3 Making a Type Searchable
Solution
Use the IComparable<T>
and IComparer<T>
interfaces. The Square
class, from Recipe 1.2, implements the IComparable<T>
interface in such a way that the List<T>
and SortedList<K,V>
collections can sort and search an array or collection of Square
objects.
Discussion
By implementing the IComparable<T>
interface on your class (or structure), you can take advantage of the search routines of the List<T>
and SortedList<K,V>
classes. The algorithms for searching are built into these classes; all you have to do is tell them how to search your classes via the code you implement in the IComparable<T>
. CompareTo
method.
To implement the CompareTo
method, see Recipe 1.2.
The List<T>
class provides a BinarySearch
method to perform a search on the elements in that list. The elements are compared against an object passed to the BinarySearch
method in the object parameter. The SortedList
class does not have a BinarySearch
method; instead, it has the ContainsKey
method, which performs a binary search on the key contained in the list. The ContainsValue
method of the SortedList
class performs a linear search when searching for values. This linear search uses the Equals
method of the elements in the SortedList
collection to do its work. The Compare
and CompareTo
methods do not have any effect on the operation of the linear search performed in the SortedList
class, but they do have an effect on binary searches.
Note
To perform an accurate search using the BinarySearch
methods of the List<T>
class, you must first sort the List<T>
using its Sort
method. In addition, if you pass an IComparer<T>
interface to the BinarySearch
method, you must also pass the same interface to the Sort
method. Otherwise, the BinarySearch
method might not be able to find the object you are looking for.
The TestSort
method shown in Example 1-4 demonstrates how to use the Square
and CompareHeight
classes with the List<Square>
and SortedList<int,Square>
collection instances.
Example 1-4. Making a type searchable
public static void TestSearch() { List<Square> listOfSquares = new List<Square> {new Square(1,3), new Square(4,3), new Square(2,1), new Square(6,1)}; IComparer<Square> heightCompare = new CompareHeight(); // Test a List<Square> Console.WriteLine("List<Square>"); Console.WriteLine("Original list"); foreach (Square square in listOfSquares) { Console.WriteLine(square.ToString()); } Console.WriteLine(); Console.WriteLine("Sorted list using IComparer<Square>=heightCompare"); listOfSquares.Sort(heightCompare); foreach (Square square in listOfSquares) { Console.WriteLine(square.ToString()); } Console.WriteLine(); Console.WriteLine("Search using IComparer<Square>=heightCompare"); int found = listOfSquares.BinarySearch(new Square(1,3), heightCompare); Console.WriteLine($"Found (1,3): {found}"); Console.WriteLine(); Console.WriteLine("Sorted list using IComparable<Square>"); listOfSquares.Sort(); foreach (Square square in listOfSquares) { Console.WriteLine(square.ToString()); } Console.WriteLine(); Console.WriteLine("Search using IComparable<Square>"); found = listOfSquares.BinarySearch(new Square(6,1)); // Use IComparable Console.WriteLine($"Found (6,1): {found}"); // Test a SortedList<Square> var sortedListOfSquares = new SortedList<int,Square>(){ {0, new Square(1,3)}, {2, new Square(4,3)}, {1, new Square(2,1)}, {4, new Square(6,1)}}; Console.WriteLine(); Console.WriteLine("SortedList<Square>"); foreach (KeyValuePair<int,Square> kvp in sortedListOfSquares) { Console.WriteLine ($"{kvp.Key} : {kvp.Value}"); } Console.WriteLine(); bool foundItem = sortedListOfSquares.ContainsKey(2); Console.WriteLine($"sortedListOfSquares.ContainsKey(2): {foundItem}"); // Does not use IComparer or IComparable // -- uses a linear search along with the Equals method // which has not been overloaded Console.WriteLine(); Square value = new Square(6,1); foundItem = sortedListOfSquares.ContainsValue(value); Console.WriteLine("sortedListOfSquares.ContainsValue " + $"(new Square(6,1)): {foundItem}"); }
This code displays the following:
List"Square> Original list Height:1 Width:3 Height:4 Width:3 Height:2 Width:1 Height:6 Width:1 Sorted list using IComparer"Square>=heightCompare Height:1 Width:3 Height:2 Width:1 Height:4 Width:3 Height:6 Width:1 Search using IComparer"Square>=heightCompare Found (1,3): 0 Sorted list using IComparable"Square> Height:2 Width:1 Height:1 Width:3 Height:6 Width:1 Height:4 Width:3 Search using IComparable"Square> Found (6,1): 2 SortedList"Square> 0 : Height:1 Width:3 1 : Height:2 Width:1 2 : Height:4 Width:3 4 : Height:6 Width:1 sortedListOfSquares.ContainsKey(2): True sortedListOfSquares.ContainsValue(new Square(6,1)): True
See Also
Recipe 1.2, and the “IComparable<T> Interface” and “IComparer<T> Interface” topics in the MSDN documentation.
1.4 Returning Multiple Items from a Method
Solution
Use the out
keyword on parameters that will act as return parameters. The following method accepts an inputShape
parameter and calculates height
, width
, and depth
from that value:
public void ReturnDimensions(int inputShape, out int height, out int width, out int depth) { height = 0; width = 0; depth = 0; // Calculate height, width, and depth from the inputShape value. }
This method would be called in the following manner:
// Declare output parameters. int height; int width; int depth; // Call method and return the height, width, and depth. Obj.ReturnDimensions(1, out height, out width, out depth);
Another method is to return a class or structure containing all the return values. The previous method has been modified here to return a structure instead of using out
arguments:
public Dimensions ReturnDimensions(int inputShape) { // The default ctor automatically defaults this structure's members to 0. Dimensions objDim = new Dimensions(); // Calculate objDim.Height, objDim.Width, objDim.Depth // from the inputShape value... return objDim; }
where Dimensions
is defined as follows:
public struct Dimensions { public int Height; public int Width; public int Depth; }
This method would now be called in this manner:
// Call method and return the height, width, and depth. Dimensions objDim = obj.ReturnDimensions(1);
Rather than returning a user-defined class or structure from this method, you can use a Tuple
object containing all the return values. The previous method has been modified here to return a Tuple
:
public Tuple<int, int, int> ReturnDimensionsAsTuple(int inputShape) { // Calculate objDim.Height, objDim.Width, objDim.Depth from the inputShape // value e.g. {5, 10, 15} // Create a Tuple with calculated values var objDim = Tuple.Create<int, int, int>(5, 10, 15); return (objDim); }
This method would now be called in this manner:
// Call method and return the height, width, and depth. Tuple<int, int, int> objDim = obj.ReturnDimensions(1);
Discussion
Marking a parameter in a method signature with the out
keyword indicates that this parameter will be initialized and returned by this method. This trick is useful when a method is required to return more than one value. A method can, at most, have only one return value, but through the use of the out
keyword, you can mark several parameters as a kind of return value.
To set up an out
parameter, mark the parameter in the method signature with the out
keyword as shown here:
public void ReturnDimensions(int inputShape, out int height, out int width, out int depth) { ... }
To call this method, you must also mark the calling method’s arguments with the out
keyword, shown here:
obj.ReturnDimensions(1, out height, out width, out depth);
The out
arguments in this method call do not have to be initialized; they can simply be declared and passed in to the ReturnDimensions
method. Regardless of whether they are initialized before the method call, they must be initialized before they are used within the ReturnDimensions
method. Even if they are not used through every path in the ReturnDimensions
method, they still must be initialized. That is why this method starts out with the following three lines of code:
height = 0; width = 0; depth = 0;
You may be wondering why you couldn’t use a ref
parameter instead of the out
parameter, as both allow a method to change the value of an argument marked as such. The answer is that an out
parameter makes the code somewhat self-documenting. You know that when an out
parameter is encountered, it is acting as a return value. In addition, an out
parameter does not require the extra work to be initialized before it is passed in to the method, while a ref
parameter does.
Note
An out
parameter does not have to be marshaled when the method is called; rather, it is marshaled once when the method returns the data to the caller. Any other type of call (by-value or by-reference using the ref
keyword) requires that the value be marshaled in both directions. Using the out
keyword in marshaling scenarios improves remoting performance.
An out
parameter is great when there are only a few values that need to be returned, but when you start encountering 4, 5, 6, or more values that need to be returned, it can get unwieldy. Another option for returning multiple values is to create and return a user-defined class/structure or to use a Tuple
to package up all the values that need to be returned by a method.
The first option, using a class/structure to return the values, is straightforward. Just create the type (in this example it is a structure) like so:
public struct Dimensions { public int Height; public int Width; public int Depth; }
Fill in each field of this structure with the required data and then return it from the method as shown in the Solution section.
The second option, using a Tuple
, is an even more elegant solution than using a user-defined object. A Tuple
can be created to hold any number of values of varying types. In addition, the data you store in the Tuple
is immutable; once you add the data to the Tuple
through the constructor or the static Create
method, that data cannot be changed.
Tuple
s can accept up to and including eight separate values. If you need to return more than eight values, you will need to use the special Tuple
class:
Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> Class
When creating a Tuple
with more than eight values, you cannot use the static Create
method—you must instead use the constructor of the class. This is how you would create a Tuple
of 10 integer values:
var values = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> ( 1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int> (8, 9, 10));
Of course, you can continue to add more Tuple
s to the end of each embedded Tuple
, creating any size Tuple
that you need.
See Also
The “Tuple Class” and “Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> Class” topics in the MSDN documentation.
1.5 Parsing Command-Line Parameters
Solution
In Example 1-5, use the following classes together to help with parsing command-line parameters: Argument
, ArgumentDefinition
, and ArgumentSemanticAnalyzer
.
Example 1-5. Argument class
using System; using System.Diagnostics; using System.Linq; using System.Collections.ObjectModel; public sealed class Argument { public string Original { get; } public string Switch { get; private set; } public ReadOnlyCollection<string> SubArguments { get; } private List<string> subArguments; public Argument(string original) { Original = original; Switch = string.Empty; subArguments = new List<string>(); SubArguments = new ReadOnlyCollection<string>(subArguments); Parse(); } private void Parse() { if (string.IsNullOrEmpty(Original)) { return; } char[] switchChars = { '/', '-' }; if (!switchChars.Contains(Original[0])) { return; } string switchString = Original.Substring(1); string subArgsString = string.Empty; int colon = switchString.IndexOf(':'); if (colon >= 0) { subArgsString = switchString.Substring(colon + 1); switchString = switchString.Substring(0, colon); } Switch = switchString; if (!string.IsNullOrEmpty(subArgsString)) subArguments.AddRange(subArgsString.Split(';')); } // A set of predicates that provide useful information about itself // Implemented using lambdas public bool IsSimple => SubArguments.Count == 0; public bool IsSimpleSwitch => !string.IsNullOrEmpty(Switch) && SubArguments.Count == 0; public bool IsCompoundSwitch => !string.IsNullOrEmpty(Switch) && SubArguments.Count == 1; public bool IsComplexSwitch => !string.IsNullOrEmpty(Switch) && SubArguments.Count > 0; } public sealed class ArgumentDefinition { public string ArgumentSwitch { get; } public string Syntax { get; } public string Description { get; } public Func<Argument, bool> Verifier { get; } public ArgumentDefinition(string argumentSwitch, string syntax, string description, Func<Argument, bool> verifier) { ArgumentSwitch = argumentSwitch.ToUpper(); Syntax = syntax; Description = description; Verifier = verifier; } public bool Verify(Argument arg) => Verifier(arg); } public sealed class ArgumentSemanticAnalyzer { private List<ArgumentDefinition> argumentDefinitions = new List<ArgumentDefinition>(); private Dictionary<string, Action<Argument>> argumentActions = new Dictionary<string, Action<Argument>>(); public ReadOnlyCollection<Argument> UnrecognizedArguments { get; private set; } public ReadOnlyCollection<Argument> MalformedArguments { get; private set; } public ReadOnlyCollection<Argument> RepeatedArguments { get; private set; } public ReadOnlyCollection<ArgumentDefinition> ArgumentDefinitions => new ReadOnlyCollection<ArgumentDefinition>(argumentDefinitions); public IEnumerable<string> DefinedSwitches => from argumentDefinition in argumentDefinitions select argumentDefinition.ArgumentSwitch; public void AddArgumentVerifier(ArgumentDefinition verifier) => argumentDefinitions.Add(verifier); public void RemoveArgumentVerifier(ArgumentDefinition verifier) { var verifiersToRemove = from v in argumentDefinitions where v.ArgumentSwitch == verifier.ArgumentSwitch select v; foreach (var v in verifiersToRemove) argumentDefinitions.Remove(v); } public void AddArgumentAction(string argumentSwitch, Action<Argument> action) => argumentActions.Add(argumentSwitch, action); public void RemoveArgumentAction(string argumentSwitch) { if (argumentActions.Keys.Contains(argumentSwitch)) argumentActions.Remove(argumentSwitch); } public bool VerifyArguments(IEnumerable<Argument> arguments) { // no parameter to verify with, fail. if (!argumentDefinitions.Any()) return false; // Identify if any of the arguments are not defined this.UnrecognizedArguments = ( from argument in arguments where !DefinedSwitches.Contains(argument.Switch.ToUpper()) select argument).ToList().AsReadOnly(); if (this.UnrecognizedArguments.Any()) return false; //Check for all the arguments where the switch matches a known switch, //but our well-formedness predicate is false. this.MalformedArguments = ( from argument in arguments join argumentDefinition in argumentDefinitions on argument.Switch.ToUpper() equals argumentDefinition.ArgumentSwitch where !argumentDefinition.Verify(argument) select argument).ToList().AsReadOnly(); if (this.MalformedArguments.Any()) return false; //Sort the arguments into "groups" by their switch, count every group, //and select any groups that contain more than one element, //We then get a read-only list of the items. this.RepeatedArguments = (from argumentGroup in from argument in arguments where !argument.IsSimple group argument by argument.Switch.ToUpper() where argumentGroup.Count() > 1 select argumentGroup).SelectMany(ag => ag).ToList().AsReadOnly(); if (this.RepeatedArguments.Any()) return false; return true; } public void EvaluateArguments(IEnumerable<Argument> arguments) { //Now we just apply each action: foreach (Argument argument in arguments) argumentActions[argument.Switch.ToUpper()](argument); } public string InvalidArgumentsDisplay() { StringBuilder builder = new StringBuilder(); builder.AppendFormat($"Invalid arguments: {Environment.NewLine}"); // Add the unrecognized arguments FormatInvalidArguments(builder, this.UnrecognizedArguments, "Unrecognized argument: {0}{1}"); // Add the malformed arguments FormatInvalidArguments(builder, this.MalformedArguments, "Malformed argument: {0}{1}"); // For the repeated arguments, we want to group them for the display, // so group by switch and then add it to the string being built. var argumentGroups = from argument in this.RepeatedArguments group argument by argument.Switch.ToUpper() into ag select new { Switch = ag.Key, Instances = ag}; foreach (var argumentGroup in argumentGroups) { builder.AppendFormat($"Repeated argument: {argumentGroup.Switch}{Environment.NewLine}"); FormatInvalidArguments(builder, argumentGroup.Instances.ToList(), "\t{0}{1}"); } return builder.ToString(); } private void FormatInvalidArguments(StringBuilder builder, IEnumerable<Argument> invalidArguments, string errorFormat) { if (invalidArguments != null) { foreach (Argument argument in invalidArguments) { builder.AppendFormat(errorFormat, argument.Original, Environment.NewLine); } } } }
Here is one example of how to use these classes to process the command line for an application:
public static void Main(string[] argumentStrings) { var arguments = (from argument in argumentStrings select new Argument(argument)).ToArray(); Console.Write("Command line: "); foreach (Argument a in arguments) { Console.Write($"{a.Original} "); } Console.WriteLine(""); ArgumentSemanticAnalyzer analyzer = new ArgumentSemanticAnalyzer(); analyzer.AddArgumentVerifier( new ArgumentDefinition("output", "/output:[path to output]", "Specifies the location of the output file.", x => x.IsCompoundSwitch)); analyzer.AddArgumentVerifier( new ArgumentDefinition("trialMode", "/trialmode", "If this is specified it places the product into trial mode", x => x.IsSimpleSwitch)); analyzer.AddArgumentVerifier( new ArgumentDefinition("DEBUGOUTPUT", "/debugoutput:[value1];[value2];[value3]", "A listing of the files the debug output " + "information will be written to", x => x.IsComplexSwitch)); analyzer.AddArgumentVerifier( new ArgumentDefinition("", "[literal value]", "A literal value", x => x.IsSimple)); if (!analyzer.VerifyArguments(arguments)) { string invalidArguments = analyzer.InvalidArgumentsDisplay(); Console.WriteLine(invalidArguments); ShowUsage(analyzer); return; } // Set up holders for the command line parsing results string output = string.Empty; bool trialmode = false; IEnumerable<string> debugOutput = null; List<string> literals = new List<string>(); //For each parsed argument, we want to apply an action, // so add them to the analyzer. analyzer.AddArgumentAction("OUTPUT", x => { output = x.SubArguments[0]; }); analyzer.AddArgumentAction("TRIALMODE", x => { trialmode = true; }); analyzer.AddArgumentAction("DEBUGOUTPUT", x => { debugOutput = x.SubArguments; }); analyzer.AddArgumentAction("", x=>{literals.Add(x.Original);}); // check the arguments and run the actions analyzer.EvaluateArguments(arguments); // display the results Console.WriteLine(""); Console.WriteLine($"OUTPUT: {output}"); Console.WriteLine($"TRIALMODE: {trialmode}"); if (debugOutput != null) { foreach (string item in debugOutput) { Console.WriteLine($"DEBUGOUTPUT: {item}"); } } foreach (string literal in literals) { Console.WriteLine($"LITERAL: {literal}"); } } public static void ShowUsage(ArgumentSemanticAnalyzer analyzer) { Console.WriteLine("Program.exe allows the following arguments:"); foreach (ArgumentDefinition definition in analyzer.ArgumentDefinitions) { Console.WriteLine($"\t{definition.ArgumentSwitch}: ({definition.Description}){Environment.NewLine} \tSyntax: {definition.Syntax}"); } }
Discussion
Before you can parse command-line parameters, you must decide upon a common format. The format for this recipe follows the command-line format for the Visual C# .NET language compiler. The format used is defined as follows:
-
All command-line arguments are separated by one or more whitespace characters.
-
Each argument may start with either a
-
or/
character, but not both. If it does not, that argument is considered a literal, such as a filename. -
Each argument that starts with either the
-
or/
character may be divided up into a switch followed by a colon followed by one or more arguments separated with the;
character. The command-line parameter-sw:arg1;arg2;arg3
is divided up into a switch (sw
) and three arguments (arg1
,arg2
, andarg3
). Note that there should not be any spaces in the full argument; otherwise, the runtime command-line parser will split up the argument into two or more arguments. -
Strings delineated with double quotes, such as
"c:\test\file.log"
, will have their double quotes stripped off. This is a function of the operating system interpreting the arguments passed in to your application. -
To preserve double quotes, precede the double quote character with the
\
escape sequence character. -
The
\
character is handled as an escape sequence character only when followed by a double quote—in which case, only the double quote is displayed. -
The
^
character is handled by the runtime command-line parser as a special character.
Fortunately, the runtime command-line parser handles most of this before your application receives the individual parsed arguments.
The runtime command-line parser passes a string[]
containing each parsed argument to the entry point of your application. The entry point can take one of the following forms:
public static void Main() public static int Main() public static void Main(string[] args) public static int Main(string[] args)
The first two accept no arguments, but the last two accept the array of parsed command-line arguments. Note that the static Environment.CommandLine
property will also return a string containing the entire command line, and the static Environment.GetCommandLineArgs
method will return an array of strings containing the parsed command-line arguments.
The three classes presented in the Solution address the phases of dealing with the command-line arguments:
Argument
- Encapsulates a single command-line argument and is responsible for parsing the argument.
ArgumentDefinition
- Defines an argument that will be valid for the current command line.
ArgumentSemanticAnalyzer
- Performs the verification and retrieval of the arguments based on the
ArgumentDefinition
s that are set up.
Passing in the following command-line arguments to this application:
MyApp c:\input\infile.txt -output:d:\outfile.txt -trialmode
results in the following parsed switches and arguments:
Command line: c:\input\infile.txt -output:d:\outfile.txt -trialmode OUTPUT: d:\outfile.txt TRIALMODE: True LITERAL: c:\input\infile.txt
If you input command-line parameters incorrectly, such as forgetting to add arguments to the -output
switch, you get the following output:
Command line: c:\input\infile.txt -output: -trialmode Invalid arguments: Malformed argument: -output Program.exe allows the following arguments: OUTPUT: (Specifies the location of the output file.) Syntax: /output:[path to output] TRIALMODE: (If this is specified, it places the product into trial mode) Syntax: /trialmode DEBUGOUTPUT: (A listing of the files the debug output information will be written to) Syntax: /debugoutput:[value1];[value2];[value3] : (A literal value) Syntax: [literal value]
There are a few items in the code that are worth pointing out.
Each Argument
instance needs to be able to determine certain things about itself; accordingly, a set of predicates that tell us useful information about this Argument
are exposed as properties on the Argument
. The ArgumentSemanticAnalyzer
will use these properties to determine the characteristics of the argument:
public bool IsSimple => SubArguments.Count == 0; public bool IsSimpleSwitch => !string.IsNullOrEmpty(Switch) && SubArguments.Count == 0; public bool IsCompoundSwitch => !string.IsNullOrEmpty(Switch) && SubArguments.Count == 1; public bool IsComplexSwitch => !string.IsNullOrEmpty(Switch) && SubArguments.Count > 0;
Note
For more information on lambda expressions, see the introduction to Chapter 4. Also see Recipe 1.16 for a discussion of using lambda expressions to implement closures.
In a number of places in the code, the ToArray
or ToList
methods are called on the result of a LINQ query:
var arguments = (from argument in argumentStrings select new Argument(argument)).ToArray();
This is because query results use deferred execution, which means that not only are the results calculated in a lazy manner, but they are recalculated every time they are accessed. Using the ToArray
or ToList
methods forces the eager evaluation of the results and generates a copy that will not reevaluate during each usage. The query logic does not know if the collection being worked on is changing or not, so it has to reevaluate each time unless you make a “point in time” copy using these methods.
To verify that these arguments are correct, we must create an ArgumentDefinition
and associate it for each acceptable argument type with the ArgumentSemanticAnalyzer
:
ArgumentSemanticAnalyzer analyzer = new ArgumentSemanticAnalyzer(); analyzer.AddArgumentVerifier( new ArgumentDefinition("output", "/output:[path to output]", "Specifies the location of the output file.", x => x.IsCompoundSwitch)); analyzer.AddArgumentVerifier( new ArgumentDefinition("trialMode", "/trialmode", "If this is specified it places the product into trial mode", x => x.IsSimpleSwitch)); analyzer.AddArgumentVerifier( new ArgumentDefinition("DEBUGOUTPUT", "/debugoutput:[value1];[value2];[value3]", "A listing of the files the debug output " + "information will be written to", x => x.IsComplexSwitch)); analyzer.AddArgumentVerifier( new ArgumentDefinition("", "[literal value]", "A literal value", x => x.IsSimple));
There are four parts to each ArgumentDefinition
: the argument switch, a string showing the syntax of the argument, a description of the argument, and the verification predicate to verify the argument. This information can be used to verify the argument, as shown here:
//Check for all the arguments where the switch matches a known switch, //but our well-formedness predicate is false. this.MalformedArguments = ( from argument in arguments join argumentDefinition in argumentDefinitions on argument.Switch.ToUpper() equals argumentDefinition.ArgumentSwitch where !argumentDefinition.Verify(argument) select argument).ToList().AsReadOnly();
The ArgumentDefinition
s also allow you to compose a usage method for the program:
public static void ShowUsage(ArgumentSemanticAnalyzer analyzer) { Console.WriteLine("Program.exe allows the following arguments:"); foreach (ArgumentDefinition definition in analyzer.ArgumentDefinitions) { Console.WriteLine("\t{0}: ({1}){2}\tSyntax: {3}", definition.ArgumentSwitch, definition.Description, Environment.NewLine,definition.Syntax); } }
To get the values of the arguments so they can be used, we need to extract the information out of the parsed arguments. For the Solution example, we would need the following information:
// Set up holders for the command line parsing results string output = string.Empty; bool trialmode = false; IEnumerable<string> debugOutput = null; List<string> literals = new List<string>();
How are these values filled in? Well, we need to associate an action with each Argument
to determine how the value should be retrieved from an Argument
instance. The action is a predicate, which makes this a very powerful approach, as any predicate can be used here. Here is where those Argument
actions are defined and associated with the ArgumentSemanticAnalyzer
:
//For each parsed argument, we want to apply an action, // so add them to the analyzer. analyzer.AddArgumentAction("OUTPUT", x => { output = x.SubArguments[0]; }); analyzer.AddArgumentAction("TRIALMODE", x => { trialmode = true; }); analyzer.AddArgumentAction("DEBUGOUTPUT", x => { debugOutput = x.SubArguments;}); analyzer.AddArgumentAction("", x=>{literals.Add(x.Original);});
Now that all of the actions are set up, we can retrieve the values by using the EvaluateArguments
method on the ArgumentSemanticAnalyzer
:
// check the arguments and run the actions analyzer.EvaluateArguments(arguments);
Now the arguments have been filled in by the execution of the actions, and the program can run with those values:
// Run the program passing in the argument values: Program program = new Program(output, trialmode, debugOutput, literals); program.Run();
The verification of the arguments uses LINQ to query for unrecognized, malformed, or repeated arguments, any of which will cause the parameters to be invalid:
public bool VerifyArguments(IEnumerable<Argument> arguments) { // no parameter to verify with, fail. if (!argumentDefinitions.Any()) return false; // Identify if any of the arguments are not defined this.UnrecognizedArguments = ( from argument in arguments where !DefinedSwitches.Contains(argument.Switch.ToUpper()) select argument).ToList().AsReadOnly(); if (this.UnrecognizedArguments.Any()) return false; //Check for all the arguments where the switch matches a known switch, //but our well-formedness predicate is false. this.MalformedArguments = ( from argument in arguments join argumentDefinition in argumentDefinitions on argument.Switch.ToUpper() equals argumentDefinition.ArgumentSwitch where !argumentDefinition.Verify(argument) select argument).ToList().AsReadOnly(); if (this.MalformedArguments.Any()) return false; //Sort the arguments into "groups" by their switch, count every group, //and select any groups that contain more than one element. //We then get a read-only list of the items. this.RepeatedArguments = (from argumentGroup in from argument in arguments where !argument.IsSimple group argument by argument.Switch.ToUpper() where argumentGroup.Count() > 1 select argumentGroup).SelectMany(ag => ag).ToList().AsReadOnly(); if (this.RepeatedArguments.Any()) return false; return true; }
Look at how much easier it is to understand each phase of the verification, compared with how it would be done before LINQ—with multiple nested loops, switches, IndexOf
s, and other mechanisms. Each query concisely states in the language of the problem domain what task it is attempting to perform.
Note
LINQ is designed to help with problems where data must be sorted, searched, grouped, filtered, and projected. Use it!
See Also
The “Main” and “Command-Line Arguments” topics in the MSDN documentation.
1.6 Initializing a Constant Field at Runtime
Solution
You have two choices when declaring a constant value in your code. You can use a readonly
field or a const
field. Each has its own strengths and weaknesses. However, if you need to initialize a constant
field at runtime, you must use a readonly
field:
public class Foo { public readonly int bar; public Foo() {} public Foo(int constInitValue) { bar = constInitValue; } // Rest of class... }
This is not possible using a const
field. A const
field can be initialized only at compile time:
public class Foo { public const int bar; // This line causes a compile-time error. public Foo() {} public Foo(int constInitValue) { bar = constInitValue; // This line also causes a compile-time error. } // Rest of class... }
Discussion
A readonly
field allows initialization to take place only in the constructor at runtime, whereas a const
field must be initialized at compile time. Therefore, implementing a readonly
field is the only way to allow a field that must be constant to be initialized at runtime.
There are only two ways to initialize a readonly
field. The first is by adding an initializer to the field itself:
public readonly int bar = 100;
The second way is to initialize the readonly
field through a constructor. This approach is demonstrated by the code in the Solution to this recipe. If you look at the following class:
public class Foo { public readonly int x; public const int y = 1; public Foo() {} public Foo(int roInitValue) { x = roInitValue; } // Rest of class... }
you’ll see it is compiled into the following IL (intermediate language):
.class auto ansi nested public beforefieldinit Foo extends [mscorlib]System.Object { .field public static literal int32 y = int32(0x00000001) //<<-- const field .field public initonly int32 x //<<-- readonly field .method public hidebysig specialname rtspecialname instance void .ctor(int32 roInitValue) cil managed { // Code size 16 (0x10) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld int32 CSharpRecipes.ClassesAndGenerics/Foo::x IL_000f: ret } // end of method Foo::.ctor .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 9 (0x9) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ret } // end of method Foo::.ctor } // End of class Foo
Notice that a const
field is compiled into a static
field, and a readonly
field is compiled into an instance
field. Therefore, you need only a class name to access a const
field.
Note
A common argument against using const
fields is that they do not version as well as readonly
fields. If you rebuild a component that defines a const
field and the value of that const
changes in a later version, any other components that were built against the old version won’t pick up the new value. If there is any chance that a field is going to change, don’t make it a const
field.
The following code shows how to use an instance readonly
field:
Foo obj1 = new Foo(100); Console.WriteLine(obj1.bar);
See Also
The “const” and “readonly” keywords in the MSDN documentation.
1.7 Building Cloneable Classes
Solution
To resolve the issue with using ICloneable
, create two other interfaces to establish a copying pattern, IShallowCopy<T>
and IDeepCopy<T>
:
public interface IShallowCopy<T> { T ShallowCopy(); } public interface IDeepCopy<T> { T DeepCopy(); }
Shallow copying means that the copied object’s fields will reference the same objects as the original object. To allow shallow copying, implement the IShallowCopy<T>
interface in the class:
using System; using System.Collections; using System.Collections.Generic; public class ShallowClone : IShallowCopy<ShallowClone> { public int Data = 1; public List<string> ListData = new List<string>(); public object ObjData = new object(); public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone(); }
Deep copying, or cloning, means that the copied object’s fields will reference new copies of the original object’s fields. To allow deep copying, implement the IDeepCopy<T>
interface in the class:
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization.Formatters.Binary; using System.IO; [Serializable] public class DeepClone : IDeepCopy<DeepClone> { public int data = 1; public List<string> ListData = new List<string>(); public object objData = new object(); public DeepClone DeepCopy() { BinaryFormatter BF = new BinaryFormatter(); MemoryStream memStream = new MemoryStream(); BF.Serialize(memStream, this); memStream.Flush(); memStream.Position = 0; return (DeepClone)BF.Deserialize(memStream); } }
To support both shallow and deep methods of copying, implement both interfaces. The code might appear as follows:
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization.Formatters.Binary; using System.IO; [Serializable] public class MultiClone : IShallowCopy<MultiClone>, IDeepCopy<MultiClone> { public int data = 1; public List<string> ListData = new List<string>(); public object objData = new object(); public MultiClone ShallowCopy() => (MultiClone)this.MemberwiseClone(); public MultiClone DeepCopy() { BinaryFormatter BF = new BinaryFormatter(); MemoryStream memStream = new MemoryStream(); BF.Serialize(memStream, this); memStream.Flush(); memStream.Position = 0; return (MultiClone)BF.Deserialize(memStream); } }
Discussion
The .NET Framework has an interface named ICloneable
, which was originally designed as the means through which cloning is implemented in .NET. The design recommendation is now that this interface not be used in any public API, because it lends itself to different interpretations. The interface looks like this:
public interface ICloneable { object Clone(); }
Notice that there is a single method, Clone
, that returns an object. Is the clone a shallow copy of the object or a deep copy? You can’t know from the interface, as the implementation could go either way. This is why it should not be used, and the IShallowCopy<T>
and IDeepCopy<T>
interfaces are introduced here.
Cloning is the ability to make an exact copy (a clone) of an instance of a type. Cloning may take one of two forms: a shallow copy or a deep copy. Shallow copying is relatively easy: it involves copying the object on which the ShallowCopy
method was called.
The reference type fields in the original object are copied over, as are the value type fields. This means that if the original object contains a field of type StreamWriter
, for instance, the cloned object will point to this same instance of the original object’s StreamWriter
; a new object is not created.
Note
There is no need to deal with static
fields when performing a cloning operation. There is only one memory location reserved for each static field per class, per application domain. The cloned object will have access to the same static fields as the original.
Support for shallow copying is implemented by the MemberwiseClone
method of the Object
class, which serves as the base class for all .NET classes. So the following code allows a shallow copy to be created and returned by the Clone
method:
public ShallowClone ShallowCopy() => (ShallowClone)this.MemberwiseClone();
Making a deep copy is the second way of cloning an object. A deep copy will make a copy of the original object just as the shallow copy does; however, a deep copy will also make separate copies of each reference type field in the original object. Therefore, if the original object contains a StreamWriter
type field, the cloned object will also contain a StreamWriter
type field, but the cloned object’s StreamWriter
field will point to a new StreamWriter
object, not that of the original object.
Support for deep copying is not automatically provided by the .NET Framework, but the following code illustrates an easy way of implementing a deep copy:
BinaryFormatter BF = new BinaryFormatter(); MemoryStream memStream = new MemoryStream(); BF.Serialize(memStream, this); memStream.Flush(); memStream.Position = 0; return (BF.Deserialize(memStream));
Basically, the original object is serialized out to a memory stream via binary serialization, and then it is deserialized into a new object, which is returned to the caller. It is important to reposition the memory stream pointer back to the start of the stream before calling the Deserialize
method; otherwise, an exception will be thrown indicating that the serialized object contains no data.
Performing a deep copy using object serialization allows you to change the underlying object without having to modify the code that performs the deep copy. If you performed the deep copy by hand, you’d have to make a new instance of all the instance fields of the original object and copy them over to the cloned object. This is a tedious chore in and of itself. If you make a change to the fields of the object being cloned, you must also change the deep copy code to reflect that modification. Using serialization, you rely on the serializer to dynamically find and serialize all fields contained in the object. If the object is modified, the serializer will still make a deep copy without any code modifications.
One reason you might want to do a deep copy by hand is that the serialization technique presented in this recipe works properly only when everything in your object is serializable. Of course, manual cloning doesn’t always help there either—some objects are just inherently uncloneable. Suppose you have a network management application in which an object represents a particular printer on your network. What’s it supposed to do when you clone it? Fax a purchase order for a new printer?
One problem inherent with deep copying is performing a deep copy on a nested data structure with circular references. This recipe makes it possible to deal with circular references, although it’s a tricky problem. So, in fact, you don’t need to avoid circular references if you are using this recipe.
See Also
Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries by Krzysztof Cwalina and Brad Abrams (Addison-Wesley Professional), and the “Object.MemberwiseClone Method” topic in the MSDN documentation.
1.8 Ensuring an Object’s Disposal
Solution
Use the using
statement:
using System; using System.IO; // ... using(FileStream FS = new FileStream("Test.txt", FileMode.Create)) { FS.WriteByte((byte)1); FS.WriteByte((byte)2); FS.WriteByte((byte)3); using(StreamWriter SW = new StreamWriter(FS)) { SW.WriteLine("some text."); } }
Discussion
The using
statement is very easy to use and saves you the hassle of writing extra code. If this Solution had not used the using
statement, it would look like this:
FileStream FS = new FileStream("Test.txt", FileMode.Create); try { FS.WriteByte((byte)1); FS.WriteByte((byte)2); FS.WriteByte((byte)3); StreamWriter SW = new StreamWriter(FS); try { SW.WriteLine("some text."); } finally { if (SW != null) { ((IDisposable)SW).Dispose(); } } } finally { if (FS != null) { ((IDisposable)FS).Dispose(); } }
Several points to note about the using
statement:
-
There is a
using
directive, such as:using System.IO;
which should be differentiated from the
using
statement. This is potentially confusing to developers first getting into this language. -
The variable(s) defined in the
using
statement clause must all be of the same type, and they must have an initializer. However, you are allowed multipleusing
statements in front of a single code block, so this isn’t a significant restriction. -
Any variables defined in the
using
clause are considered read-only in the body of theusing
statement. This prevents a developer from inadvertently switching the variable to refer to a different object and causing problems when attempting to dispose of the object that the variable initially referenced. -
The variable should not be declared outside of the
using
block and then initialized inside of theusing
clause.
This last point is described by the following code:
FileStream FS; using(FS = new FileStream("Test.txt", FileMode.Create)) { FS.WriteByte((byte)1); FS.WriteByte((byte)2); FS.WriteByte((byte)3); using(StreamWriter SW = new StreamWriter(FS)) { SW.WriteLine("some text."); } }
For this example code, you will not have a problem. But consider that the variable FS
is usable outside of the using
block. Essentially, you could revisit this code and modify it as follows:
FileStream FS; using(FS = new FileStream("Test.txt", FileMode.Create)) { FS.WriteByte((byte)1); FS.WriteByte((byte)2); FS.WriteByte((byte)3); using(StreamWriter SW = new StreamWriter(FS)) { SW.WriteLine("some text."); } } FS.WriteByte((byte)4);
This code compiles but throws an ObjectDisposedException
on the last line of this code snippet because the Dispose
method has already been called on the FS
object. The object has not yet been collected at this point and still remains in memory in the disposed state.
See Also
The “Cleaning Up Unmanaged Resources,” “IDisposable Interface,” “Using foreach with Collections,” and “Implementing Finalize and Dispose to Clean Up Unmanaged Resources” topics in the MSDN documentation.
1.9 Deciding When and Where to Use Generics
Solution
In deciding when and where to use generic types, you need to consider several things:
-
Will your type contain or be operating on various unspecified data types (e.g., a collection type)? If so, creating a generic type will offer several benefits over creating a nongeneric type. If your type will operate on only a single specific type, then you may not need to create a generic type.
-
If your type will be operating on value types, so that boxing and unboxing operations will occur, you should consider using generics to prevent the performance penalty incurred from boxing and unboxing operations.
-
The stronger type checking associated with generics will aid in finding errors sooner (i.e., during compile time as opposed to runtime), thus shortening your bug-fixing cycle.
-
Is your code suffering from “code bloat,” with you writing multiple classes to handle different data types on which they operate (e.g., a specialized
ArrayList
that stores onlyStreamReader
s and another that stores onlyStreamWriter
s)? It is easier to write the code once and have it just work for each of the data types it operates on. -
Generics allow for greater clarity of code. By eliminating code bloat and forcing stronger type checking on your types, they make your code easier to read and understand.
See Also
The “Generics Overview” and “Benefits of Generics” topics in the MSDN documentation.
1.10 Understanding Generic Types
Solution
A couple of quick experiments can show the differences between regular .NET types and generic .NET types. Before we get deep into the code, if you are unfamiliar with generics, jump to the Discussion section in this recipe for a detailed explanation about generics and then come back to this section.
Now, when a regular .NET type is defined, it looks like the FixedSizeCollection
type defined in Example 1-6.
Example 1-6. FixedSizeCollection: a regular .NET type
public class FixedSizeCollection { /// <summary> /// Constructor that increments static counter /// and sets the maximum number of items /// </summary> /// <param name="maxItems"></param> public FixedSizeCollection(int maxItems) { FixedSizeCollection.InstanceCount++; this.Items = new object[maxItems]; } /// <summary> /// Add an item to the class whose type /// is unknown as only object can hold any type /// </summary> /// <param name="item">item to add</param> /// <returns>the index of the item added</returns> public int AddItem(object item) { if (this.ItemCount < this.Items.Length) { this.Items[this.ItemCount] = item; return this.ItemCount++; } else throw new Exception("Item queue is full"); } /// <summary> /// Get an item from the class /// </summary> /// <param name="index">the index of the item to get</param> /// <returns>an item of type object</returns> public object GetItem(int index) { if (index >= this.Items.Length && index >= 0) throw new ArgumentOutOfRangeException(nameof(index)); return this.Items[index]; } #region Properties /// <summary> /// Static instance counter hangs off of the Type for /// StandardClass /// </summary> public static int InstanceCount { get; set; } /// <summary> /// The count of the items the class holds /// </summary> public int ItemCount { get; private set; } /// <summary> /// The items in the class /// </summary> private object[] Items { get; set; } #endregion // Properties /// <summary> /// ToString override to provide class detail /// </summary> /// <returns>formatted string with class details</returns> public override string ToString() => $"There are {FixedSizeCollection.InstanceCount.ToString()} instances of {this.GetType().ToString()} and this instance contains {this.ItemCount} items..."; }
FixedSizeCollection
has a static integer property variable, InstanceCount
, which is incremented in the instance constructor, and a ToString
override that prints out how many instances of FixedSizeCollection
exist in this AppDomain.FixedSizeCollection
. Additionally, this collection class contains an array of objects(Items)
, the size of which is determined by the item count passed in to the constructor. FixedSizeCollection
also implements methods that add and retrieve items (AddItem
, GetItem
) and a read-only property to get the number of items currently in the array (ItemCount
).
FixedSizeCollection<T>
is a generic .NET type with the same static property InstanceCount
field, the instance constructor that counts the number of instantiations, and the overridden ToString
method to tell you how many instances there are of this type. FixedSizeCollection<T>
also has an Items
array property and methods corresponding to those in FixedSizeCollection
, as you can see in Example 1-7.
Example 1-7. FixedSizeCollection<T>: a generic .NET type
/// <summary> /// A generic class to show instance counting /// </summary> /// <typeparam name="T">the type parameter used for the array storage</typeparam> public class FixedSizeCollection<T> { /// <summary> /// Constructor that increments static counter and sets up internal storage /// </summary> /// <param name="items"></param> public FixedSizeCollection(int items) { FixedSizeCollection<T>.InstanceCount++; this.Items = new T[items]; } /// <summary> /// Add an item to the class whose type /// is determined by the instantiating type /// </summary> /// <param name="item">item to add</param> /// <returns>the zero-based index of the item added</returns> public int AddItem(T item) { if (this.ItemCount < this.Items.Length) { this.Items[this.ItemCount] = item; return this.ItemCount++; } else throw new Exception("Item queue is full"); } /// <summary> /// Get an item from the class /// </summary> /// <param name="index">the zero-based index of the item to get</param> /// <returns>an item of the instantiating type</returns> public T GetItem(int index) { if (index >= this.Items.Length && index >= 0) throw new ArgumentOutOfRangeException(nameof(index)); return this.Items[index]; } #region Properties /// <summary> /// Static instance counter hangs off of the /// instantiated Type for /// GenericClass /// </summary> public static int InstanceCount { get; set; } /// <summary> /// The count of the items the class holds /// </summary> public int ItemCount { get; private set; } /// <summary> /// The items in the class /// </summary> private T[] Items { get; set; } #endregion // Properties /// <summary> /// ToString override to provide class detail /// </summary> /// <returns>formatted string with class details</returns> public override string ToString() => $"There are {FixedSizeCollection<T>.InstanceCount.ToString()} instances of {this.GetType().ToString()} and this instance contains {this.ItemCount} items..."; }
Things start to differ a little with FixedSizeCollection<T>
when you look at the Items
array property implementation. The Items
array is declared as:
private T[] Items { get; set; }
instead of:
private object[] Items { get; set; }
The Items
array property uses the type parameter of the generic class (<T>
) to determine what types of items are allowed. FixedSizeCollection
uses object
for the Items
array property type, which allows any type to be stored in the array of items (since all types are convertible to object
), while FixedSizeCollection<T>
provides type safety by allowing the type parameter to dictate what types of objects are permitted. Notice also that the properties have no associated private backing field declared for storing the array. This is an example of using the new automatically implemented properties feature that was originally introduced in C# 3.0. Under the covers, the C# compiler is creating a storage element of the property’s type, but you don’t have to write the code for the property storage anymore if you don’t have specific code that has to execute when accessing the properties. To make the property read-only, simply mark the set;
declaration private
.
The next difference is visible in the method declarations of AddItem
and GetItem
. AddItem
now takes a parameter of type T
, whereas in FixedSizeCollection
, it took a parameter of type object
. GetItem
now returns a value of type T
, whereas in FixedSizeCollection
, it returned a value of type object
. These changes allow the methods in FixedSizeCollection<T>
to use the instantiated type to store and retrieve the items in the array, instead of having to allow any object
to be stored as in FixedSizeCollection
:
/// <summary> /// Add an item to the class whose type /// is determined by the instantiating type /// </summary> /// <param name="item">item to add</param> /// <returns>the zero-based index of the item added</returns> public int AddItem(T item) { if (this.ItemCount < this.Items.Length) { this.Items[this.ItemCount] = item; return this.ItemCount++; } else throw new Exception("Item queue is full"); } /// <summary> /// Get an item from the class /// </summary> /// <param name="index">the zero-based index of the item to get</param> /// <returns>an item of the instantiating type</returns> public T GetItem(int index) { if (index >= this.Items.Length && index >= 0) throw new ArgumentOutOfRangeException("index"); return this.Items[index]; }
This provides a few advantages, first and foremost of which is the type safety provided by FixedSizeCollection<T>
for items in the array. It was possible to write code like this in FixedSizeCollection
:
// Regular class FixedSizeCollection C = new FixedSizeCollection(5); Console.WriteLine(C); string s1 = "s1"; string s2 = "s2"; string s3 = "s3"; int i1 = 1; // Add to the fixed size collection (as object). C.AddItem(s1); C.AddItem(s2); C.AddItem(s3); // Add an int to the string array, perfectly OK. C.AddItem(i1);
But FixedSizeCollection<T>
will give a compiler error if you try the same thing:
// Generic class FixedSizeCollection<string> gC = new FixedSizeCollection<string>(5); Console.WriteLine(gC); string s1 = "s1"; string s2 = "s2"; string s3 = "s3"; int i1 = 1; // Add to the generic class (as string). gC.AddItem(s1); gC.AddItem(s2); gC.AddItem(s3); // Try to add an int to the string instance, denied by compiler. // error CS1503: Argument '1': cannot convert from 'int' to 'string' //gC.AddItem(i1);
Having the compiler prevent this before it can become the source of runtime bugs is a very good idea.
It may not be immediately noticeable, but the integer is actually boxed when it is added to the object array in FixedSizeCollection
, as you can see in the IL for the call to GetItem
on FixedSizeCollection
:
IL_0177: ldloc.2 IL_0178: ldloc.s i1 IL_017a: box [mscorlib]System.Int32 IL_017f: callvirt instance int32 CSharpRecipes.ClassesAndGenerics/FixedSizeCollection::AddItem(object)
This boxing turns the int
, which is a value type, into a reference type (object
) for storage in the array. This requires you to do extra work to store value types in the object
array.
You’ll encounter another problem when you go to retrieve an item from the class in the FixedSizeCollection
implementation. Take a look at how FixedSizeCollection.GetItem
retrieves an item:
// Hold the retrieved string. string sHolder; // Have to cast or get error CS0266: // Cannot implicitly convert type 'object' to 'string' sHolder = (string)C.GetItem(1);
Since the item returned by FixedSizeCollection.GetItem
is of type object
, you need to cast it to a string
in order to get what you hope is a string
for index 1. It may not be a string
—all you know for sure is that it’s an object
—but you have to cast it to a more specific type coming out so you can assign it properly.
These issues are both fixed by the FixedSizeCollection<T>
implementation. Unlike with FixedSizeCollection
, no unboxing is required in FixedSizeCollection<T>
, since the return type of GetItem
is the instantiated type, and the compiler enforces this by looking at the value being returned:
// Hold the retrieved string. string sHolder; int iHolder; // No cast necessary sHolder = gC.GetItem(1); // Try to get a string into an int. // error CS0029: Cannot implicitly convert type 'string' to 'int' //iHolder = gC.GetItem(1);
To see one other difference between the two types, instantiate a few instances of each like so:
// Regular class FixedSizeCollection A = new FixedSizeCollection(5); Console.WriteLine(A); FixedSizeCollection B = new FixedSizeCollection(5); Console.WriteLine(B); FixedSizeCollection C = new FixedSizeCollection(5); Console.WriteLine(C); // generic class FixedSizeCollection<bool> gA = new FixedSizeCollection<bool>(5); Console.WriteLine(gA); FixedSizeCollection<int> gB = new FixedSizeCollection<int>(5); Console.WriteLine(gB); FixedSizeCollection<string> gC = new FixedSizeCollection<string>(5); Console.WriteLine(gC); FixedSizeCollection<string> gD = new FixedSizeCollection<string>(5); Console.WriteLine(gD);
The output from the preceding code shows this:
There are 1 instances of CSharpRecipes.ClassesAndGenerics+FixedSizeCollection and this instance contains 0 items... There are 2 instances of CSharpRecipes.ClassesAndGenerics+FixedSizeCollection and this instance contains 0 items... There are 3 instances of CSharpRecipes.ClassesAndGenerics+FixedSizeCollection and this instance contains 0 items... There are 1 instances of CSharpRecipes.ClassesAndGenerics+FixedSizeCollection'1 [System.Boolean] and this instance contains 0 items... There are 1 instances of CSharpRecipes.ClassesAndGenerics+FixedSizeCollection'1 [System.Int32] and this instance contains 0 items... There are 1 instances of CSharpRecipes.ClassesAndGenerics+FixedSizeCollection'1 [System.String] and this instance contains 0 items... There are 2 instances of CSharpRecipes.ClassesAndGenerics+FixedSizeCollection'1 [System.String] and this instance contains 0 items...
Discussion
The type parameters in generics allow you to create type-safe code without knowing the final type you will be working with. In many instances, you want the types to have certain characteristics, in which case you place constraints on the type (see Recipe 1.12). Methods can have generic type parameters whether or not the class itself does.
Notice that while FixedSizeCollection
has three instances, FixedSizeCollection<T>
has one instance in which it was declared with bool
as the type, one instance in which int
was the type, and two instances in which string
was the type. This means that, while there is one .NET Type
object created for each nongeneric class, there is one .NET Type
object for every constructed type of a generic class.
FixedSizeCollection
has three instances in the example code because FixedSizeCollection
has only one type that is maintained by the CLR. With generics, one type is maintained for each combination of the class template and the type arguments passed when a type instance is constructed. In other words, you get one .NET type for FixedSizeCollection<bool>
, one .NET type for FixedSizeCollection<int>
, and a third .NET type for FixedSizeCollection<string>
.
The static InstanceCount
property helps to illustrate this point, as static properties of a class are actually connected to the type that the CLR hangs on to. The CLR creates any given type only once and then maintains it until the AppDomain
unloads. This is why the output from the calls to ToString
on these objects shows that the count is 3
for FixedSizeCollection
(as there is truly only one of these) and 1
or 2
for the FixedSizeCollection<T>
types.
See Also
The “Generic Type Parameters” and “Generic Classes” topics in the MSDN documentation.
1.11 Reversing the Contents of a Sorted List
Problem
You want to be able to reverse the contents of a sorted list of items while maintaining the ability to access them in both array and list styles like SortedList
and the generic SortedList<T>
classes provide. Neither SortedList
nor SortedList<T>
provides a direct way to accomplish this without reloading the list.
Solution
Use LINQ to Objects to query the SortedList<T>
and apply a descending order to the information in the list. After you instantiate a SortedList<TKey, TValue>
, the key of which is an int
and the value of which is a string
, a series of unordered numbers and their text representations are inserted into the list. Those items are then displayed:
SortedList<int, string> data = new SortedList<int, string>() { [2]="two", [5]="five", [3]="three", [1]="one" }; foreach (KeyValuePair<int, string> kvp in data) { Console.WriteLine($"\t {kvp.Key}\t{kvp.Value}"); }
The output for the list is shown sorted in ascending order (the default):
1 one 2 two 3 three 5 five
Now you reverse the sort order by creating a query using LINQ to Objects and setting the orderby
clause to descending
. The results are then displayed from the query result set:
// query ordering by descending var query = from d in data orderby d.Key descending select d; foreach (KeyValuePair<int, string> kvp in query) { Console.WriteLine($"\t {kvp.Key}\t{kvp.Value}"); }
This time the output is in descending order:
5 five 3 three 2 two 1 one
When you add a new item to the list, it is added in the ascending sort order, but by querying again after adding all of the items, you keep the ordering of the list intact:
data.Add(4, "four"); // requery ordering by descending query = from d in data orderby d.Key descending select d; foreach (KeyValuePair<int, string> kvp in query) { Console.WriteLine($"\t {kvp.Key}\t{kvp.Value}"); } Console.WriteLine(""); // Just go against the original list for ascending foreach (KeyValuePair<int, string> kvp in data) { Console.WriteLine($"\t {kvp.Key}\t{kvp.Value}"); }
You can see the output in both descending and ascending order with the new item:
5 five 4 four 3 three 2 two 1 one 1 one 2 two 3 three 4 four 5 five
Discussion
A SortedList
blends array and list syntax to allow you to access the data in either format, which can be a handy thing to do. The data is accessible as key/value pairs or directly by index and will not allow you to add duplicate keys. In addition, values that are reference or nullable types can be null
, but keys cannot. You can iterate over the items using a foreach
loop, with KeyValuePair
being the type returned. While accessing elements of the SortedList<T>
, you may only read from them. The usual iterator syntax prohibits you from updating or deleting elements of the list while reading, as it will invalidate the iterator.
The orderby
clause in the query orders the result set of the query either in ascending
(the default) or descending
order. This sorting is accomplished through use of the default comparer for the element type, so you can alter it by overriding the Equals
method for elements that are custom classes. You can specify multiple keys for the orderby
clause, which has the effect of nesting the sort order, such as sorting by “last name” and then “first name.”
See Also
The “SortedList,” “Generic KeyValuePair Structure,” and “Generic SortedList” topics in the MSDN documentation.
1.12 Constraining Type Arguments
Solution
Use constraints to force the type arguments of a generic type to be of a type that implements one or more particular interfaces:
public class DisposableList<T> : IList<T> where T : class, IDisposable { private List<T> _items = new List<T>(); // Private method that will dispose of items in the list private void Delete(T item) => item.Dispose(); // IList<T> Members public int IndexOf(T item) => _items.IndexOf(item); public void Insert(int index, T item) => _items.Insert(index, item); public T this[int index] { get {return (_items[index]);} set {_items[index] = value;} } public void RemoveAt(int index) { Delete(this[index]); _items.RemoveAt(index); } // ICollection<T> Members public void Add(T item) => _items.Add(item); public bool Contains(T item) => _items.Contains(item); public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); public int Count => _items.Count; public bool IsReadOnly => false; // IEnumerable<T> Members public IEnumerator<T> GetEnumerator()=> _items.GetEnumerator(); // IEnumerable Members IEnumerator IEnumerable.GetEnumerator()=> _items.GetEnumerator(); // Other members public void Clear() { for (int index = 0; index < _items.Count; index++) { Delete(_items[index]); } _items.Clear(); } public bool Remove(T item) { int index = _items.IndexOf(item); if (index >= 0) { Delete(_items[index]); _items.RemoveAt(index); return (true); } else { return (false); } } }
This DisposableList
class allows only an object that implements IDisposable
to be passed in as a type argument to this class. The reason for this is that whenever an object is removed from a DisposableList
object, the Dispose
method is always called on that object. This allows you to transparently handle the management of any object stored within this DisposableList
object.
The following code exercises a DisposableList
object:
public static void TestDisposableListCls() { DisposableList<StreamReader> dl = new DisposableList<StreamReader>(); // Create a few test objects. StreamReader tr1 = new StreamReader("C:\\Windows\\system.ini"); StreamReader tr2 = new StreamReader("c:\\Windows\\vmgcoinstall.log"); StreamReader tr3 = new StreamReader("c:\\Windows\\Starter.xml"); // Add the test object to the DisposableList. dl.Add(tr1); dl.Insert(0, tr2); dl.Add(tr3); foreach(StreamReader sr in dl) { Console.WriteLine($"sr.ReadLine() == {sr.ReadLine()}"); } // Call Dispose before any of the disposable objects are // removed from the DisposableList. dl.RemoveAt(0); dl.Remove(tr1); dl.Clear(); }
Discussion
The where
keyword is used to constrain a type parameter to accept only arguments that satisfy the given constraint. For example, the DisposableList
has the constraint that any type argument T
must implement the IDisposable
interface:
public class DisposableList<T> : IList<T> where T : IDisposable
This means that the following code will compile successfully:
DisposableList<StreamReader> dl = new DisposableList<StreamReader>();
but the following code will not:
DisposableList<string> dl = new DisposableList<string>();
This is because the string
type does not implement the IDisposable
interface, and the StreamReader
type does.
Other constraints on the type argument are allowed, in addition to requiring one or more specific interfaces to be implemented. You can force a type argument to be inherited from a specific base class, such as the TextReader
class:
public class DisposableList<T> : IList<T> where T : System.IO.TextReader, IDisposable
You can also determine if the type argument is narrowed down to only value types or only reference types. The following class declaration is constrained to using only value types:
public class DisposableList<T> : IList<T> where T : struct
This class declaration is constrained to only reference types:
public class DisposableList<T> : IList<T> where T : class
In addition, you can also require any type argument to implement a public default constructor:
public class DisposableList<T> : IList<T> where T : IDisposable, new()
Using constraints allows you to write generic types that accept a narrower set of available type arguments. If the IDisposable
constraint is omitted in the Solution for this recipe, a compile-time error will occur. This is because not all of the types that can be used as the type argument for the DisposableList
class will implement the IDisposable
interface. If you skip this compile-time check, a DisposableList
object may contain objects that do not have a public no-argument Dispose
method. In this case, a runtime exception will occur. Generics and constraints in particular force strict type checking of the class-type arguments and allow you to catch these problems at compile time rather than at runtime.
See Also
The “where Keyword” topic in the MSDN documentation.
1.13 Initializing Generic Variables to Their Default Values
Solution
Simply use the default
keyword to initialize that variable to its default value:
public class DefaultValueExample<T> { T data = default(T); public bool IsDefaultData() { T temp = default(T); if (temp.Equals(data)) { return (true); } else { return (false); } } public void SetData(T val) => data = value; }
The code to exercise this class is shown here:
public static void ShowSettingFieldsToDefaults() { DefaultValueExample<int> dv = new DefaultValueExample<int>(); // Check if the data is set to its default value; true is returned. bool isDefault = dv.IsDefaultData(); Console.WriteLine($"Initial data: {isDefault}"); // Set data. dv.SetData(100); // Check again, this time a false is returned. isDefault = dv.IsDefaultData(); Console.WriteLine($"Set data: {isDefault}"); }
The first call to IsDefaultData
returns true
, while the second returns false
. The output is shown here:
Initial data: True Set data: False
Discussion
When initializing a variable of the same type parameter as the generic class, you cannot just set that variable to null
. What if the type parameter is a value type such as an int
or char
? This will not work because value types cannot be null
. You may be thinking that a nullable
type such as long?
or Nullable<long>
can be set to null
(see “Using Nullable Types (C# Programming Guide)” in the MSDN documentation for more on nullable types). However, the compiler has no way of knowing what type argument the user will use to construct the type.
The default
keyword allows you to tell the compiler that at compile time the default value of this variable should be used. If the type argument supplied is a numeric value (e.g., int
, long
, decimal
), then the default value is 0
. If the type argument supplied is a reference type, then the default value is null
. If the type argument supplied is a struct, then you determine the default value by initializing each member field to its default value.
See Also
The “Using Nullable Types (C# Programming Guide)” and “default Keyword in Generic Code” topics in the MSDN documentation.
1.14 Adding Hooks to Generated Entities
Solution
Use partial methods to add hooks in the generated code for the business entities.
The process to generate the entities may be from UML (Unified Modeling Language), a data set, or some other object-modeling facility, but when the code is generated as partial classes, add partial method hooks into the templates for the properties that call a ChangingProperty
partial method, as shown in the GeneratedEntity
class:
public partial class GeneratedEntity { public GeneratedEntity(string entityName) { this.EntityName = entityName; } partial void ChangingProperty(string name, string originalValue, stringnewValue); public string EntityName { get; } private string _FirstName; public string FirstName { get { return _FirstName; } set { ChangingProperty("FirstName",_FirstName,value); _FirstName = value; } } private string _State; public string State { get { return _State; } set { ChangingProperty("State",_State,value); _State = value; } } }
The GeneratedEntity
has two properties, FirstName
and State
. Notice each of these properties has the same boilerplate code that calls the ChangingProperty
method with the name of the property, the original, and the new values. If the generated class is used at this point, the ChangingProperty
declaration and method will be removed by the compiler, as there is no implementation for ChangingProperty
. If an implementation is supplied to report on property changes as shown here, then all of the partial method code for ChangingProperty
will be retained and executed:
public partial class GeneratedEntity { partial void ChangingProperty(string name, string originalValue, string newValue) { Console.WriteLine($"Changed property ({name}) for entity " + $"{this.EntityName} from " + $"{originalValue} to {newValue}"); } }
Discussion
When using partial methods, be aware of the following:
-
You indicate a partial method with the
partial
modifier. -
Partial methods can be declared only in partial classes.
-
Partial methods might have only a declaration and no body.
-
From a signature standpoint, a partial method can have arguments, require a void return value, and must not have any access modifier, and
partial
implies that this method is private and can be static, generic, or unsafe. -
For generic partial methods, constraints must be repeated on the declaring and implementing versions.
-
A partial method may not implement an interface member since interface members must be public.
-
None of the virtual, abstract, override, new, sealed, or extern modifiers may be used.
-
Arguments to a partial method cannot use
out
, but they can useref
.
Partial methods are similar to conditional methods, except that the method definition is always present in conditional methods, even when the condition is not met. Partial methods do not retain the method definition if there is no matching implementation. The code in the Solution could be used like this:
public static void TestPartialMethods() { Console.WriteLine("Start entity work"); GeneratedEntity entity = new GeneratedEntity("FirstEntity"); entity.FirstName = "Bob"; entity.State = "NH"; GeneratedEntity secondEntity = new GeneratedEntity("SecondEntity"); entity.FirstName = "Jay"; secondEntity.FirstName = "Steve"; secondEntity.State = "MA"; entity.FirstName = "Barry"; secondEntity.State = "WA"; secondEntity.FirstName = "Matt"; Console.WriteLine("End entity work"); }
to produce the following output when the ChangingProperty
implementation is provided:
Start entity work Changed property (FirstName) for entity FirstEntity from to Bob Changed property (State) for entity FirstEntity from to NH Changed property (FirstName) for entity FirstEntity from Bob to Jay Changed property (FirstName) for entity SecondEntity from to Steve Changed property (State) for entity SecondEntity from to MA Changed property (FirstName) for entity FirstEntity from Jay to Barry Changed property (State) for entity SecondEntity from MA to WA Changed property (FirstName) for entity SecondEntity from Steve to Matt End entity work
or to produce the following output when the ChangingProperty
implementation is not provided:
Start entity work End entity work
See Also
The “Partial Methods” and “partial (Method)” topics in the MSDN documentation.
1.15 Controlling How a Delegate Fires Within a Multicast Delegate
Problem
You have combined multiple delegates to create a multicast delegate. When this multicast delegate is invoked, each delegate within it is invoked in turn. You need to exert more control over the order in which each delegate is invoked, firing only a subset of delegates, or firing each delegate based on the success or failure of previous delegates. Additionally, you need to be able to handle the return value of each delegate separately.
Solution
Use the GetInvocationList
method to obtain an array of Delegate
objects. Next, iterate over this array using a for
(if enumerating in a nonstandard order) or foreach
(for enumerating in a standard order) loop. You can then invoke each Delegate
object in the array individually and, optionally, retrieve each delegate’s unique return value.
In C#, all delegate types support multicast—that is, any delegate instance can invoke multiple methods each time the instance is invoked if it has been set up to do so. In this recipe, we use the term multicast to describe a delegate that has been set up to invoke multiple methods.
The following method creates a multicast delegate called allInstances
and then uses GetInvocationList
to allow each delegate to be invoked individually, in reverse order. The Func<int>
generic delegate is used to create delegate instances that return an int
:
public static void InvokeInReverse() { Func<int> myDelegateInstance1 = TestInvokeIntReturn.Method1; Func<int> myDelegateInstance2 = TestInvokeIntReturn.Method2; Func<int> myDelegateInstance3 = TestInvokeIntReturn.Method3; Func<int> allInstances = myDelegateInstance1 + myDelegateInstance2 + myDelegateInstance3; Console.WriteLine("Fire delegates in reverse"); Delegate[] delegateList = allInstances.GetInvocationList(); foreach (Func<int> instance in delegateList.Reverse()) { instance(); } }
Note that to roll over the delegate list retrieved using GetInvocationList
, we use the IEnumerable<T>
extension method Reverse
so that we get the items in the opposite order of how the enumeration would normally produce them.
As the following methods demonstrate by firing every other delegate, you don’t have to invoke all of the delegates in the list. InvokeEveryOtherOperation
uses an extension method created here for IEnumerable<T>
called EveryOther
that will return only every other item from the enumeration.
Note
If a unicast delegate was used and you called GetInvocationList
on it, you will receive a list of one delegate instance.
public static void InvokeEveryOtherOperation() { Func<int> myDelegateInstance1 = TestInvokeIntReturn.Method1; Func<int> myDelegateInstance2 = TestInvokeIntReturn.Method2; Func<int> myDelegateInstance3 = TestInvokeIntReturn.Method3; Func<int> allInstances = myDelegateInstance1 + myDelegateInstance2 + myDelegateInstance3; Delegate[] delegateList = allInstances.GetInvocationList(); Console.WriteLine("Invoke every other delegate"); foreach (Func<int> instance in delegateList.EveryOther()) { // invoke the delegate int retVal = instance(); Console.WriteLine($"Delegate returned {retVal}"); } } static IEnumerable<T> EveryOther<T>(this IEnumerable<T> enumerable) { bool retNext = true; foreach (T t in enumerable) { if (retNext) yield return t; retNext = !retNext; } }
The following class contains each of the methods that will be called by the multicast delegate allInstances
:
public class TestInvokeIntReturn { public static int Method1() { Console.WriteLine("Invoked Method1"); return 1; } public static int Method2() { Console.WriteLine("Invoked Method2"); return 2; } public static int Method3() { Console.WriteLine("Invoked Method3"); return 3; } }
You can also specify whether to continue firing delegates in the list based on the return value of the currently firing delegate. The following method fires each delegate, stopping only when a delegate returns a false
value:
public static void InvokeWithTest() { Func<bool> myDelegateInstanceBool1 = TestInvokeBoolReturn.Method1; Func<bool> myDelegateInstanceBool2 = TestInvokeBoolReturn.Method2; Func<bool> myDelegateInstanceBool3 = TestInvokeBoolReturn.Method3; Func<bool> allInstancesBool = myDelegateInstanceBool1 + myDelegateInstanceBool2 + myDelegateInstanceBool3; Console.WriteLine( "Invoke individually (Call based on previous return value):"); foreach (Func<bool> instance in allInstancesBool.GetInvocationList()) { if (!instance()) break; } }
The following class contains each of the methods that will be called by the multicast delegate allInstancesBool
:
public class TestInvokeBoolReturn { public static bool Method1() { Console.WriteLine("Invoked Method1"); return true; } public static bool Method2() { Console.WriteLine("Invoked Method2"); return false; } public static bool Method3() { Console.WriteLine("Invoked Method3"); return true; } }
Discussion
A delegate, when called, will invoke all delegates stored within its invocation list. These delegates are usually invoked sequentially from the first to the last one added. Using the GetInvocationList
method of the MulticastDelegate
class, you can obtain each delegate in the invocation list of a multicast delegate. This method accepts no parameters and returns an array of Delegate
objects that corresponds to the invocation list of the delegate on which this method was called. The returned Delegate
array contains the delegates of the invocation list in the order in which they would normally be called; that is, the first element in the Delegate
array contains the Delegate
object that is normally called first.
This application of the GetInvocationList
method enables you to control exactly when and how the delegates in a multicast delegate are invoked, and to prevent the continued invocation of delegates when one delegate fails. This ability is important if each delegate is manipulating data, and one of the delegates fails in its duties but does not throw an exception. If one delegate fails in its duties and the remaining delegates rely on all previous delegates to succeed, you must quit invoking delegates at the point of failure.
This recipe handles a delegate failure more efficiently and also provides more flexibility in dealing with these errors. For example, you can write logic to specify which delegates are to be invoked, based on the return values of previously invoked delegates. The following method creates a multicast delegate called All
and then uses GetInvocationList
to fire each delegate individually. After firing each delegate, it captures the return value:
public static void TestIndividualInvokesReturnValue() { Func<int> myDelegateInstance1 = TestInvokeIntReturn.Method1; Func<int> myDelegateInstance2 = TestInvokeIntReturn.Method2; Func<int> myDelegateInstance3 = TestInvokeIntReturn.Method3; Func<int> allInstances = myDelegateInstance1 + myDelegateInstance2 + myDelegateInstance3; Console.WriteLine("Invoke individually (Obtain each return value):"); foreach (Func<int> instance in allInstances.GetInvocationList()) { int retVal = instance(); Console.WriteLine($"\tOutput: {retVal}"); } }
One quirk of a multicast delegate is that if any or all delegates within its invocation list return a value, only the value of the last invoked delegate is returned; all others are lost. This loss can become annoying—or worse, if your code requires these return values. Consider a case in which the allInstances
delegate was invoked normally, as in the following code:
retVal = allInstances(); Console.WriteLine(retVal);
The value 3
would be displayed because Method3
was the last method invoked by the allInstances
delegate. None of the other return values would be captured.
By using the GetInvocationList
method of the MulticastDelegate
class, you can get around this limitation. This method returns an array of Delegate
objects that can each be invoked separately. Note that this method does not invoke each delegate; it simply returns an array of them to the caller. By invoking each delegate separately, you can retrieve each return value from each invoked delegate.
Note that any out
or ref
parameters will also be lost when a multicast delegate is invoked. This recipe allows you to obtain the out
and/or ref
parameters of each invoked delegate within the multicast delegate.
However, you still need to be aware that any unhandled exceptions emanating from one of these invoked delegates will be bubbled up to the method TestIndividualInvokesReturnValue
presented in this recipe. If an exception does occur in a delegate that is invoked from within a multicast delegate and that exception is unhandled, any remaining delegates are not invoked. This is the expected behavior of a multicast delegate. However, in some circumstances, you’d like to be able to handle exceptions thrown from individual delegates and then determine at that point whether to continue invoking the remaining delegates.
Note
An unhandled exception will force the invocation of delegates to cease. Exceptions should be used only for exceptional circumstances, not for control flow.
In the following TestIndividualInvokesExceptions
method, if an exception is caught it is logged to the event log and displayed, and then the code continues to invoke delegates:
public static void TestIndividualInvokesExceptions() { Func<int> myDelegateInstance1 = TestInvokeIntReturn.Method1; Func<int> myDelegateInstance2 = TestInvokeIntReturn.Method2; Func<int> myDelegateInstance3 = TestInvokeIntReturn.Method3; Func<int> allInstances = myDelegateInstance1 + myDelegateInstance2 + myDelegateInstance3; Console.WriteLine("Invoke individually (handle exceptions):"); // Create an instance of a wrapper exception to hold any exceptions // encountered during the invocations of the delegate instances List<Exception> invocationExceptions = new List<Exception>(); foreach (Func<int> instance in allInstances.GetInvocationList()) { try { int retVal = instance(); Console.WriteLine($"\tOutput: {retVal}"); } catch (Exception ex) { // Display and log the exception and continue Console.WriteLine(ex.ToString()); EventLog myLog = new EventLog(); myLog.Source = "MyApplicationSource"; myLog.WriteEntry( $"Failure invoking {instance.Method.Name} with error " + $"{ex.ToString()}", EventLogEntryType.Error); // add this exception to the list invocationExceptions.Add(ex); } } // if we caught any exceptions along the way, throw our // wrapper exception with all of them in it. if (invocationExceptions.Count > 0) { throw new MulticastInvocationException(invocationExceptions); } }
The MulticastInvocationException
class, used in the previous code, can have multiple exceptions added to it. It exposes a ReadOnlyCollection<Exception>
through the InvocationExceptions
property, as shown here:
[Serializable] public class MulticastInvocationException : Exception { private List<Exception> _invocationExceptions; public MulticastInvocationException() : base() { } public MulticastInvocationException( IEnumerable<Exception> invocationExceptions) { _invocationExceptions = new List<Exception>(invocationExceptions); } public MulticastInvocationException(string message) : base(message) { } public MulticastInvocationException(string message, Exception innerException) :base(message,innerException) { } protected MulticastInvocationException(SerializationInfo info, StreamingContext context) : base(info, context) { _invocationExceptions = (List<Exception>)info.GetValue("InvocationExceptions", typeof(List<Exception>)); } [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] public override void GetObjectData( SerializationInfo info, StreamingContext context) { info.AddValue("InvocationExceptions", this.InvocationExceptions); base.GetObjectData(info, context); } public ReadOnlyCollection<Exception> InvocationExceptions => new ReadOnlyCollection<Exception>(_invocationExceptions); }
This strategy allows for as fine-grained handling of exceptions as you need. One option is to store all of the exceptions that occur during delegate processing, and then wrap all of the exceptions encountered during processing in a custom exception. After processing completes, throw the custom exception.
By adding a finally
block to this try
-catch
block, you can be assured that code within this finally
block is executed after every delegate returns. This technique is useful if you want to interleave code between calls to delegates, such as code to clean up objects that are not needed or code to verify that each delegate left the data it touched in a stable state.
See Also
The “Delegate Class” and “Delegate.GetInvocationList Method” topics in the MSDN documentation.
1.16 Using Closures in C#
Solution
Use lambda expressions to implement closures, functions that capture the state of the environment that is in scope where they are declared. Put more simply, closures are current state plus some behavior that can read and modify that state. Lambda expressions have the capacity to capture external variables and extend their lifetime, which makes closures possible in C#.
Note
For more information on lambda expressions, see the introduction to Chapter 4.
As an example, you will build a quick reporting system that tracks sales personnel and their revenue production versus commissions. The closure behavior is that you can build one bit of code that does the commission calculations per quarter and works on every salesperson.
First, you have to define your sales personnel:
class SalesPerson { // CTOR's public SalesPerson() { } public SalesPerson(string name, decimal annualQuota, decimal commissionRate) { this.Name = name; this.AnnualQuota = annualQuota; this.CommissionRate = commissionRate; } // Private Members decimal _commission; // Properties public string Name { get; set; } public decimal AnnualQuota { get; set; } public decimal CommissionRate { get; set; } public decimal Commission { get { return _commission; } set { _commission = value; this.TotalCommission += _commission; } } public decimal TotalCommission {get; private set; } }
Sales personnel have a name, an annual quota, a commission rate for sales, and some storage for holding a quarterly commission and a total commission. Now that you have something to work with, let’s write a bit of code to do the work of calculating the commissions:
delegate void CalculateEarnings(SalesPerson sp); static CalculateEarnings GetEarningsCalculator(decimal quarterlySales, decimal bonusRate) { return salesPerson => { // Figure out the salesperson's quota for the quarter. decimal quarterlyQuota = (salesPerson.AnnualQuota / 4); // Did he make quota for the quarter? if (quarterlySales < quarterlyQuota) { // Didn't make quota, no commission salesPerson.Commission = 0; } // Check for bonus-level performance (200% of quota). else if (quarterlySales > (quarterlyQuota * 2.0m)) { decimal baseCommission = quarterlyQuota * salesPerson.CommissionRate; salesPerson.Commission = (baseCommission + ((quarterlySales - quarterlyQuota) * (salesPerson.CommissionRate * (1 + bonusRate)))); } else // Just regular commission { salesPerson.Commission = salesPerson.CommissionRate * quarterlySales; } }; }
You’ve declared the delegate type as CalculateEarnings
, and it takes a SalesPerson
type. You have a factory method to construct an instance of this delegate for you, called GetEarningsCalculator
, which creates a lambda expression to calculate the SalesPerson
’s commission and returns a CalculateEarnings
instantiation.
To get started, create your array of salespeople:
// set up the salespeople... SalesPerson[] salesPeople = { new SalesPerson { Name="Chas", AnnualQuota=100000m, CommissionRate=0.10m }, new SalesPerson { Name="Ray", AnnualQuota=200000m, CommissionRate=0.025m }, new SalesPerson { Name="Biff", AnnualQuota=50000m, CommissionRate=0.001m }};
Then set up the earnings calculators based on quarterly earnings:
public class QuarterlyEarning { public string Name { get; set; } public decimal Earnings { get; set; } public decimal Rate { get; set; } } QuarterlyEarning[] quarterlyEarnings = { new QuarterlyEarning(){ Name="Q1", Earnings = 65000m, Rate = 0.1m }, new QuarterlyEarning(){ Name="Q2", Earnings = 20000m, Rate = 0.1m }, new QuarterlyEarning(){ Name="Q3", Earnings = 37000m, Rate = 0.1m }, new QuarterlyEarning(){ Name="Q4", Earnings = 110000m, Rate = 0.15m} }; var calculators = from e in quarterlyEarnings select new { Calculator = GetEarningsCalculator(e.Earnings, e.Rate), QuarterlyEarning = e };
Finally, run the numbers for each quarter for all the salespeople, and then you can generate the annual report from this data by calling WriteCommissionReport
. This will tell the executives which sales personnel are worth keeping:
decimal annualEarnings = 0; foreach (var c in calculators) { WriteQuarterlyReport(c.QuarterlyEarning.Name, c.QuarterlyEarning.Earnings, c.Calculator, salesPeople); annualEarnings += c.QuarterlyEarning.Earnings; } // Let's see who is worth keeping... WriteCommissionReport(annualEarnings, salesPeople);
WriteQuarterlyReport
invokes the CalculateEarnings
lambda expression implementation (eCalc
) for every SalesPerson
and modifies the state to assign quarterly commission values based on the commission rates for each one:
static void WriteQuarterlyReport(string quarter, decimal quarterlySales, CalculateEarnings eCalc, SalesPerson[] salesPeople) { Console.WriteLine($"{quarter} Sales Earnings on Quarterly Sales of {quarterlySales.ToString("C")}:"); foreach (SalesPerson salesPerson in salesPeople) { // Calc commission eCalc(salesPerson); // Report Console.WriteLine($"\tSales person {salesPerson.Name} " + "made a commission of : " + $"{salesPerson.Commission.ToString("C")}"); } }
WriteCommissionReport
checks the revenue earned by the individual salesperson against his commission, and if his commission is more than 20 percent of the revenue he generated, you recommend action be taken:
static void WriteCommissionReport(decimal annualEarnings, SalesPerson[] salesPeople) { decimal revenueProduced = ((annualEarnings) / salesPeople.Length); Console.WriteLine(""); Console.WriteLine($"Annual Earnings were {annualEarnings.ToString("C")}"); Console.WriteLine(""); var whoToCan = from salesPerson in salesPeople select new { // if his commission is more than 20% // of what he produced, can him CanThem = (revenueProduced * 0.2m) < salesPerson.TotalCommission, salesPerson.Name, salesPerson.TotalCommission }; foreach (var salesPersonInfo in whoToCan) { Console.WriteLine($"\t\tPaid {salesPersonInfo.Name} " + $"{salesPersonInfo.TotalCommission.ToString("C")} to produce" + $"{revenueProduced.ToString("C")}"); if (salesPersonInfo.CanThem) { Console.WriteLine($"\t\t\tFIRE {salesPersonInfo.Name}!"); } } }
The output for your revenue- and commission-tracking program is listed here for your enjoyment:
Q1 Sales Earnings on Quarterly Sales of $65,000.00: SalesPerson Chas made a commission of : $6,900.00 SalesPerson Ray made a commission of : $1,625.00 SalesPerson Biff made a commission of : $70.25 Q2 Sales Earnings on Quarterly Sales of $20,000.00: SalesPerson Chas made a commission of : $0.00 SalesPerson Ray made a commission of : $0.00 SalesPerson Biff made a commission of : $20.00 Q3 Sales Earnings on Quarterly Sales of $37,000.00: SalesPerson Chas made a commission of : $3,700.00 SalesPerson Ray made a commission of : $0.00 SalesPerson Biff made a commission of : $39.45 Q4 Sales Earnings on Quarterly Sales of $110,000.00: SalesPerson Chas made a commission of : $12,275.00 SalesPerson Ray made a commission of : $2,975.00 SalesPerson Biff made a commission of : $124.63 Annual Earnings were $232,000.00 Paid Chas $22,875.00 to produce $77,333.33 FIRE Chas! Paid Ray $4,600.00 to produce $77,333.33 Paid Biff $254.33 to produce $77,333.33
Discussion
One of the best descriptions of closures in C# is to think of an object as a set of methods associated with data and to think of a closure as a set of data associated with a function. If you need to have several different operations on the same data, an object approach may make more sense. These are two different angles on the same problem, and the type of problem you are solving will help you decide which is the right approach. It just depends on your inclination as to which way to go. There are times when 100% pure object-oriented programming can get tedious and is unnecessary, and closures are a nice way to solve some of those problems. The SalesPerson
commission example presented here is a demonstration of what you can do with closures. It could have been done without them, but at the expense of writing more class and method code.
Closures were defined earlier, but there is a stricter definition that essentially implies that the behavior associated with the state should not be able to modify the state in order to be a true closure. We tend to agree more with the first definition, as it expresses what a closure should be, not how it should be implemented, which seems too restrictive. Whether you choose to think of this approach as a neat side feature of lambda expressions or you feel it is worthy of being called a closure, it is another programming trick for your toolbox and should not be dismissed.
See Also
Recipe 1.17 and the “Lambda Expressions” topic in the MSDN documentation.
1.17 Performing Multiple Operations on a List Using Functors
Solution
Use a functor (or function object) as the vehicle for transforming the collection. A functor is any object that can be called as a function. Examples are a delegate, a function, a function pointer, or even an object that defines operator
for us C/C++ converts.
Needing to perform multiple operations on a collection is a reasonably common scenario in software. Let’s say that you have a stock portfolio with a bunch of stocks in it. Your StockPortfolio
class would have a List
of Stock
objects and would allow you to add stocks:
public class StockPortfolio : IEnumerable<Stock> { List<Stock> _stocks; public StockPortfolio() { _stocks = new List<Stock>(); } public void Add(string ticker, double gainLoss) { _stocks.Add(new Stock() {Ticker=ticker, GainLoss=gainLoss}); } public IEnumerable<Stock> GetWorstPerformers(int topNumber) => _stocks.OrderBy((Stock stock) => stock.GainLoss).Take(topNumber); public void SellStocks(IEnumerable<Stock> stocks) { foreach(Stock s in stocks) _stocks.Remove(s); } public void PrintPortfolio(string title) { Console.WriteLine(title); _stocks.DisplayStocks(); } #region IEnumerable<Stock> Members public IEnumerator<Stock> GetEnumerator() => _stocks.GetEnumerator(); #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); #endregion }
The Stock
class is rather simple. You just need a ticker symbol for the stock and its percentage of gain or loss:
public class Stock { public double GainLoss { get; set; } public string Ticker { get; set; } }
To use this StockPortfolio
, you add a few stocks to it with gain/loss percentages and print out your starting portfolio. Once you have the portfolio, you want to get a list of the three worst-performing stocks, so you can improve your portfolio by selling them and print out your portfolio again:
StockPortfolio tech = new StockPortfolio() { {"OU81", -10.5}, {"C#6VR", 2.0}, {"PCKD", 12.3}, {"BTML", 0.5}, {"NOVB", -35.2}, {"MGDCD", 15.7}, {"GNRCS", 4.0}, {"FNCTR", 9.16}, {"LMBDA", 9.12}, {"PCLS", 6.11}}; tech.PrintPortfolio("Starting Portfolio"); // sell the worst 3 performers var worstPerformers = tech.GetWorstPerformers(3); Console.WriteLine("Selling the worst performers:"); worstPerformers.DisplayStocks(); tech.SellStocks(worstPerformers); tech.PrintPortfolio("After Selling Worst 3 Performers");
So far, nothing terribly interesting is happening. Let’s take a look at how you figured out what the three worst performers were by looking at the internals of the GetWorstPerformers
method:
public IEnumerable<Stock> GetWorstPerformers(int topNumber) => _stocks.OrderBy( (Stock stock) => stock.GainLoss).Take(topNumber);
First you make sure the list is sorted with the worst-performing stocks at the front by calling the OrderBy
extension method on IEnumerable<T>
. The OrderBy
method takes a lambda expression that provides the gain/loss percentage for comparison for the number of stocks indicated by topNumber
in the Take
extension method.
GetWorstPerformers
returns an IEnumerable<Stock>
full of the three worst performers. Since they aren’t making any money, you should cash in and sell them. For your purposes, selling is simply removing them from the list of stocks in StockPortfolio
. To accomplish this, you use yet another functor to iterate over the list of stocks handed to the SellStocks
function (the list of worst-performing ones, in your case), and then remove that stock from the internal list that the StockPortfolio
class maintains:
public void SellStocks(IEnumerable<Stock> stocks) { foreach(Stock s in stocks) _stocks.Remove(s); }
Discussion
Functors come in a few different flavors: a generator (a function with no parameters), a unary function (a function with one parameter), and a binary function (a function with two parameters). If the functor happens to return a Boolean value, then it gets an even more special naming convention: a unary function that returns a Boolean is called a predicate, and a binary function with a Boolean return is called a binary predicate. There are both Predicate<T>
and BinaryPredicate<T>
delegates defined in the Framework to facilitate these uses of functors.
The List<T>
and System.Array
classes take predicates (Predicate<T>
, BinaryPredicate<T>
), actions (Action<T>
), comparisons (Comparison<T>
), and converters (Converter<T,U>
). This allows these collections to be operated on in a much more general way than was previously possible.
Thinking in terms of functors can be challenging at first, but once you put a bit of time into it, you start to see powerful possibilities open up before you. Any code you can write once, debug once, and use many times is valuable, and functors can help you achieve that.
The output for the example is listed here:
Starting Portfolio (OU81) lost 10.5% (C#6VR) gained 2% (PCKD) gained 12.3% (BTML) gained 0.5% (NOVB) lost 35.2% (MGDCD) gained 15.7% (GNRCS) gained 4% (FNCTR) gained 9.16% (LMBDA) gained 9.12% (PCLS) gained 6.11% Selling the worst performers: (NOVB) lost 35.2% (OU81) lost 10.5% (BTML) gained 0.5% After Selling Worst 3 Performers (C#6VR) gained 2% (PCKD) gained 12.3% (MGDCD) gained 15.7% (GNRCS) gained 4% (FNCTR) gained 9.16% (LMBDA) gained 9.12% (PCLS) gained 6.11%
See Also
The “System.Collections.Generic.List<T>,” “System.Linq.Enumerable Class,” and “System.Array” topics in the MSDN documentation.
1.18 Controlling Struct Field Initialization
Problem
You need to be able to control the initialization of a struct depending on whether you want the struct to initialize all of its internal fields to their standard default values based on their type (e.g., int
is initialized to 0
and string
is initialized to an empty string), to a nonstandard set of default values, or to a set of predefined values.
Solution
We can use the various constructors for a struct to accomplish our goals. To initialize all the internal fields in a struct to their standard default values based on their type, we simply use the default initialization of structs, which will be demonstrated later. To initialize the struct’s fields to a set of predefined values, we use an overloaded constructor. Finally, to initialize our struct to a set of nonstandard default values, we need to use optional arguments in the struct’s constructor. With optional arguments, structs are able to set their internal fields based on the default values placed on the optional arguments in the constructor’s parameter list.
The data structure in Example 1-8 uses an overloaded constructor to initialize all the fields of the structure.
Example 1-8. Struct with an overloaded constructor
public struct Data { public Data(int intData, float floatData, string strData, char charData, bool boolData) { IntData = intData; FloatData = floatData; StrData = strData; CharData = charData; BoolData = boolData; } public int IntData { get; } public float FloatData { get; } public string StrData { get; } public char CharData { get; } public bool BoolData { get; } public override string ToString()=> IntData + " :: " + FloatData + " :: " + StrData + " :: " + CharData + " :: " + BoolData; }
This is the typical way to initialize the values of the struct’s fields. Note also that an implicit default constructor exists that allows this struct to initialize its fields to their default values. However, you may want to have each field initialized with nondefault values. The data structure in Example 1-9 uses an overloaded constructor with optional arguments to initialize all the fields of the structure with nondefault values.
Example 1-9. Struct with optional arguments in the constructor
public struct Data { public Data(int intData, float floatData = 1.1f, string strData = "a", char charData = 'a', bool boolData = true) : this() { IntData = intData; FloatData = floatData; StrData = strData; CharData = charData; BoolData = boolData; } public int IntData { get; } public float FloatData { get; } public string StrData { get; } public char CharData { get; } public bool BoolData { get; } public override string ToString()=> IntData + " :: " + FloatData + " :: " + StrData + " :: " + CharData + " :: " + BoolData; }
Of course, a new initialization method could be introduced that makes this even easier. But you need to explicitly call it, as shown in Example 1-10.
Example 1-10. Struct with an explicit initialization method
public struct Data { public void Init() { IntData = 2; FloatData = 1.1f; StrData = "AA"; CharData = 'A'; BoolData = true; } public int IntData { get; private set; } public float FloatData { get; private set; } public string StrData { get; private set; } public char CharData { get; private set; } public bool BoolData { get; private set; } public override string ToString()=> IntData + " :: " + FloatData + " :: " + StrData + " :: " + CharData + " :: " + BoolData; }
Note that when using an explicit initialization method such as Init
, you’ll need to add a private property setter for each property in order for each field to be initialized.
Discussion
We can now create instances of the struct in Example 1-8 using different techniques. Each technique uses a different method of initializing this struct object. The first technique uses the default
keyword to create this struct:
Data dat = default(Data);
The default
keyword simply creates an instance of this struct with all of its fields initialized to their default values. Essentially this causes all numeric types to default to 0
, bool
defaults to false
, char
defaults to '\0'
, and string
and other reference types default to null
.
Now, this is great if you don’t mind reference types and char
to be set to null
values, but say that you need to set these types to something other than null
when the struct is created. The second technique for creating an instance of this struct does just this; it uses a default parameterless constructor:
Data dat = new Data();
This code causes the default parameterless constructor to be invoked. The caveat with using a default parameterless constructor on a struct is that the new
keyword must be used to create an instance of this struct. If the new
keyword is not used, then this default constructor will not be invoked. Therefore, the following code will not call the default parameterless constructor:
Data[] dat = new Data[4];
Rather, the system-defined default values for each of the struct’s fields will be used.
There are two ways to get around this. You could use the overly lengthy way of creating an array of Data
structs:
Data[] dat = new Data[4]; dat[0] = new Data(); dat[1] = new Data(); dat[2] = new Data(); dat[3] = new Data();
or
ArrayList dat = new ArrayList(); dat.Add(new Data()); dat.Add(new Data()); dat.Add(new Data()); dat.Add(new Data());
Or you could use the more terse option, which uses LINQ:
Data[] dataList = new Data[4]; dataList = (from d in dataList select new Data()).ToArray();
The LINQ expression iterates over the Data
array, explicitly invoking the default parameterless constructor for each Data
type struct element.
If neither of the first two options will work for your particular case, you could always create an overloaded constructor that takes arguments for each field that you want to initialize. This third technique requires that the overloaded constructor is used to create a new instance of this struct:
public Data(int intData, float floatData, string strData, char charData, bool boolData) { IntData = intData; FloatData = floatData; StrData = strData; CharData = charData; BoolData = boolData; }
This constructor explicitly initialized each field to a user-supplied value:
Data dat = new Data(2, 2.2f, "blank", 'a', false);
With C# 6.0 you not only have the option of initializing a struct’s fields with the system default values or using an overloaded constructor to initialize its fields to user-defined values, but now you have the additional option of using an overloaded constructor with optional arguments to initialize the struct’s fields to nonsystem-default values. This is shown in Example 1-9. The constructor with optional arguments looks like this:
public Data(int intData, float floatData = 1.1f, string strData = "a", char charData = 'a', bool boolData = true) : this() { ... }
The one issue with using this type of constructor is that you must supply at least one of the parameter values to this constructor. If the intData
argument also had an associated optional argument:
public Data(int intData = 2, float floatData = 1.1f, string strData = "a", char charData = 'a', bool boolData = true) : this() { ... }
then this code:
Data dat = new Data();
would call the default parameterless constructor for the struct, not the overloaded constructor. This is why at least one of the parameters must be passed into this constructor:
Data dat = new Data(3);
Now we call the overloaded constructor, setting the first parameter, intData
, to 3
and the rest of the parameters to their optional values.
As a final option, you can add an explicit initialization method to the struct to initialize the fields to nondefault values. This technique is shown in Example 1-10.
You add the Init
method to the struct, and must call it after the struct is initialized either by using the new
or default
keyword. The Init
method then initializes each field to a nondefault value. The only other code modification that you need to make to the struct’s properties is adding a private setter method. This allows the Init
method to set the internal fields without having to expose them to the outside world.
See Also
The “Struct” topics in the MSDN documentation.
1.19 Checking for null in a More Concise Way
Solution
Use the new null
-conditional operator introduced in C# 6.0. In the past you would typically have to check to make sure an object is not null
before using it in the following manner:
if (val != null) { val.Trim().ToUpper(); ... }
Now you can simply use the null
-conditional operator:
val?.Trim().ToUpper();
This simplified syntax determines if val
is null
; if so, the Trim
and ToUpper
methods will not be invoked and you will not throw that annoying NullReferenceException
. If val
is not null
, the Trim
and ToUpper
methods will be invoked.
This operator can also be employed to test each object for null
when the dot operator is used to chain a series of object member accesses:
Person?.Address?.State?.Trim();
In this case, if any of the first three objects (Person
, Address
, or State
) is null
, the dot operator is not invoked for that null
object and execution of this expression ceases.
The null
-conditional operator works not only on regular objects, but also on arrays and indexes as well as the indexed element that is returned. For example, if val
is of type string[]
, this code will check to see if the val
variable is null
:
val?[0].ToUpper();
whereas this code checks to see if the actual string element stored in the zeroth indexed position in the val
array is null
:
val[0]?.ToUpper();
This code is also valid; it determines if both val
and the zeroth indexed element are not null
:
val?[0]?.ToUpper();
Another area where the null
-conditional operator shines is with invoking delegates and events. For instance, if you have a simple delegate:
public delegate bool Approval();
and instantiate it using a lambda expression, which for simplicity’s sake just returns true
all the time:
Approval approvalDelegate = () => { return true; };
then later in the code when you want to invoke this delegate you don’t have to write any bulky conditional code to determine whether it is null
; you simply use the null
-conditional operator:
approvalDelegate?.Invoke()
Discussion
Essentially the null
-conditional operator works similarly to the ternary operator (?:
). The code:
val?.Trim();
is shorthand for:
(val != null ) ? (string)val.Trim() : null
assuming val
is of type string
.
Let’s take a look at what happens when a value type is returned such as in the following code:
val?.Length;
The expression is modified to return a nullable value type such as int?
:
(val != null ) ? (int?)val.Length : null
This means you can’t simply use the null
-conditional operator and then assign the returned value to just any type—it has to be a nullable type. Therefore, this code will not compile:
int len = val?.Length;
but this code will:
int? len = val?.Length;
Notice that we have to make the return type a nullable type only when it is a value type.
Additionally, you cannot attempt to use the null
-conditional operator where a nonnullable type is expected. For example, the array size expects an int
value, so you cannot compile this code:
byte[] data = new byte[val?.Length];
However, you could use the GetValueOrDefault
method to convert the nullable type’s value into a non-nullable-friendly value:
byte[] data = new byte[(val?.Length).GetValueOrDefault()];
This way if val
is really null
, the byte
array will be initialized to the default value for integer types, which is 0
. Just be aware that this method will return the default value for that value type, which is 0
for numeric types and false
for bool
types. Your code must take this into account so that your application’s behavior is consistent. In this example, the byte
array is of size 0 if the val
object is of length 0 or is null
, so your application logic must account for that.
You also need to take care when using this operator in conditional statements:
if (val?.Length > 0) Console.WriteLine("val.length > 0"); else Console.WriteLine("val.length = 0 or null");
In this conditional statement, if the val
variable is non-null
and its length is greater than 0, the true block of the if
statement is executed and the text "val.length > 0"
is displayed. If val
is null
, the false block is displayed and the text "val.length = 0 or null"
is displayed. However, you don’t know which val
really is—null
or 0
?
If you need to check for val
having a length of 0, you could add an extra check to the if
-else
statement to take into account all conditions:
if (val?.Length > 0) Console.WriteLine("val.Length > 0"); else if (val?.Length == 0) Console.WriteLine("val.Length = 0"); else Console.WriteLine("val.Length = null");
The switch
statement operates in a similar manner:
switch (val?.Length) { case 0: Console.WriteLine("val.Length = 0"); break; case 1: Console.WriteLine("val.Length = 1"); break; default: Console.WriteLine("val.Length > 1 or val.Length = null"); break; }
If val
is null
, execution will fall through to the default
block. You won’t know if the length of val
is greater than 1 or null
unless you perform more checks.
Warning
Take care when using this operator in conditional statements. This can lead to logic errors in your code if you are not careful.
See Also
The “Null-Conditional Operator” topics in the MSDN documentation.
Get C# 6.0 Cookbook, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.