Pro C#10 CHAPTER 11 Advanced C# Language Features

CHAPTER 11

Advanced C# Language Features

In this chapter, you’ll deepen your understanding of the C# programming language by examining several more advanced topics. To begin, you’ll learn how to implement and use an indexer method. This C# mechanism enables you to build custom types that provide access to internal subitems using an array-like syntax. After you learn how to build an indexer method, you’ll see how to overload various operators (+, -,
<, >, etc.) and how to create custom explicit and implicit conversion routines for your types (and you’ll learn
why you might want to do this).
Next, you’ll examine topics that are particularly useful when working with LINQ-centric APIs (though you can use them outside of the context of LINQ)—specifically extension methods and anonymous types.
To wrap things up, you’ll learn how to create an “unsafe” code context to directly manipulate unmanaged pointers. While it is certainly true that using pointers in C# applications is an infrequent activity, understanding how to do so can be helpful in some circumstances that involve complex interoperability scenarios.

Understanding Indexer Methods
As a programmer, you are certainly familiar with the process of accessing individual items contained within a simple array using the index operator ([]). Here’s an example:

// Loop over incoming command-line arguments
// using index operator.
for(int i = 0; i < args.Length; i++)
{
Console.WriteLine("Args: {0}", args[i]);
}

// Declare an array of local integers. int[] myInts = { 10, 9, 100, 432, 9874};

// Use the index operator to access each element. for(int j = 0; j < myInts.Length; j++)
{
Console.WriteLine("Index {0} = {1} ", j, myInts[j]);
}
Console.ReadLine();

© Andrew Troelsen, Phil Japikse 2022
A. Troelsen and P. Japikse, Pro C# 10 with .NET 6, https://doi.org/10.1007/978-1-4842-7869-7_11

427

This code is by no means a major news flash. However, the C# language provides the capability to design custom classes and structures that may be indexed just like a standard array, by defining an indexer method. This feature is most useful when you are creating custom collection classes (generic or nongeneric).
Before examining how to implement a custom indexer, let’s begin by seeing one in action. Assume you have added support for an indexer method to the custom PersonCollection type developed in
Chapter 10 (specifically, the IssuesWithNonGenericCollections project). While you have not yet added the indexer, observe the following usage within a new Console Application project named SimpleIndexer:

using System.Data; using SimpleIndexer;

// Indexers allow you to access items in an array-like fashion. Console.WriteLine(" Fun with Indexers \n");

PersonCollection myPeople = new PersonCollection();

// Add objects with indexer syntax.
myPeople[0] = new Person("Homer", "Simpson", 40); myPeople[1] = new Person("Marge", "Simpson", 38); myPeople[2] = new Person("Lisa", "Simpson", 9); myPeople[3] = new Person("Bart", "Simpson", 7); myPeople[4] = new Person("Maggie", "Simpson", 2);

// Now obtain and display each item using indexer.
for (int i = 0; i < myPeople.Count; i++)
{
Console.WriteLine("Person number: {0}", i); Console.WriteLine("Name: {0} {1}",
myPeople[i].FirstName, myPeople[i].LastName); Console.WriteLine("Age: {0}", myPeople[i].Age); Console.WriteLine();
}

As you can see, indexers allow you to manipulate the internal collection of subobjects just like a standard array. Now for the big question: how do you configure the PersonCollection class (or any custom class or structure) to support this functionality? An indexer is represented as a slightly modified C# property definition. In its simplest form, an indexer is created using the this[] syntax. Here is the required update for the PersonCollection class:
using System.Collections; namespace SimpleIndexer;
// Add the indexer to the existing class definition. public class PersonCollection : IEnumerable
{
private ArrayList arPeople = new ArrayList();

// Custom indexer for this class. public Person this[int index]

{
get => (Person)arPeople[index];
set => arPeople.Insert(index, value);
}
}

Apart from using the this keyword with the brackets, the indexer looks just like any other C# property declaration. For example, the role of the get scope is to return the correct object to the caller. Here, you are doing so by delegating the request to the indexer of the ArrayList object, as this class also supports an
indexer. The set scope oversees adding new Person objects; this is achieved by calling the Insert() method of the ArrayList.
Indexers are yet another form of syntactic sugar, given that this functionality can also be achieved using “normal” public methods such as AddPerson() or GetPerson(). Nevertheless, when you support indexer methods on your custom collection types, they integrate well into the fabric of the .NET base class libraries.
While creating indexer methods is quite commonplace when you are building custom collections, do remember that generic types give you this functionality out of the box. Consider the following method, which uses a generic List of Person objects. Note that you can simply use the indexer of List directly. Here’s an example:

static void UseGenericListOfPeople()
{
List myPeople = new List(); myPeople.Add(new Person("Lisa", "Simpson", 9)); myPeople.Add(new Person("Bart", "Simpson", 7));

// Change first person with indexer.
myPeople[0] = new Person("Maggie", "Simpson", 2);

// Now obtain and display each item using indexer. for (int i = 0; i < myPeople.Count; i++)
{
Console.WriteLine("Person number: {0}", i);
Console.WriteLine("Name: {0} {1}", myPeople[i].FirstName, myPeople[i].LastName);
Console.WriteLine("Age: {0}", myPeople[i].Age); Console.WriteLine();
}
}

Indexing Data Using String Values
The current PersonCollection class defined an indexer that allowed the caller to identify subitems using a numerical value. Understand, however, that this is not a requirement of an indexer method. Suppose you’d prefer to contain the Person objects using a System.Collections.Generic.Dictionary<TKey, TValue> rather than an ArrayList. Given that Dictionary types allow access to the contained types using a key (such as a person’s first name), you could define an indexer as follows:

using System.Collections; namespace SimpleIndexer;
public class PersonCollectionStringIndexer : IEnumerable
{
private Dictionary<string, Person> listPeople = new Dictionary<string, Person>();

// This indexer returns a person based on a string index. public Person this[string name]
{
get => (Person)listPeople[name]; set => listPeople[name] = value;
}
public void ClearPeople()
{
listPeople.Clear();
}

public int Count => listPeople.Count;

IEnumerator IEnumerable.GetEnumerator() => listPeople.GetEnumerator();
}

The caller would now be able to interact with the contained Person objects, as shown here:
Console.WriteLine(" Fun with Indexers \n"); PersonCollectionStringIndexer myPeopleStrings =
new PersonCollectionStringIndexer();

myPeopleStrings["Homer"] =
new Person("Homer", "Simpson", 40); myPeopleStrings["Marge"] =
new Person("Marge", "Simpson", 38);

// Get "Homer" and print data.
Person homer = myPeopleStrings["Homer"]; Console.ReadLine();

Again, if you were to use the generic Dictionary<TKey, TValue> type directly, you’d gain the indexer method functionality out of the box, without building a custom, nongeneric class supporting a string indexer. Nevertheless, do understand that the data type of any indexer will be based on how the supporting collection type allows the caller to retrieve subitems.

Overloading Indexer Methods
Indexer methods may be overloaded on a single class or structure. Thus, if it makes sense to allow the caller to access subitems using a numerical index or a string value, you might define multiple indexers for a single type. By way of example, in ADO.NET (.NET’s native database-access API), the DataSet class supports a property named Tables, which returns to you a strongly typed DataTableCollection type. As it turns out, DataTableCollection defines three indexers to get and set DataTable objects—one by ordinal position and the others by a friendly string moniker and optional containing namespace, as shown here:

public sealed class DataTableCollection : InternalDataCollectionBase
{

// Overloaded indexers!
public DataTable this[int index] { get; }

public DataTable this[string name] { get; }
public DataTable this[string name, string tableNamespace] { get; }
}

It is common for types in the base class libraries to support indexer methods. So be aware, even if your current project does not require you to build custom indexers for your classes and structures, that many types already support this syntax.

Indexers with Multiple Dimensions
You can also create an indexer method that takes multiple parameters. Assume you have a custom collection that stores subitems in a 2D array. If this is the case, you may define an indexer method as follows:

public class SomeContainer
{
private int[,] my2DintArray = new int[10, 10];

public int this[int row, int column]
{ / get or set value from 2D array / }
}

Again, unless you are building a highly stylized custom collection class, you won’t have much need to build a multidimensional indexer. Still, once again ADO.NET showcases how useful this construct can be. The ADO.NET DataTable is essentially a collection of rows and columns, much like a piece of graph paper or the general structure of a Microsoft Excel spreadsheet.
While DataTable objects are typically populated on your behalf using a related “data adapter,” the following code illustrates how to manually create an in-memory DataTable containing three columns (for the first name, last name, and age of each record). Notice how once you have added a single row to the DataTable, you use a multidimensional indexer to drill into each column of the first (and only) row. (If you are following along, you’ll need to import the System.Data namespace into your code file.)

static void MultiIndexerWithDataTable()
{
// Make a simple DataTable with 3 columns. DataTable myTable = new DataTable(); myTable.Columns.Add(new DataColumn("FirstName")); myTable.Columns.Add(new DataColumn("LastName")); myTable.Columns.Add(new DataColumn("Age"));

// Now add a row to the table. myTable.Rows.Add("Mel", "Appleby", 60);

// Use multidimension indexer to get details of first row. Console.WriteLine("First Name: {0}", myTable.Rows[0][0]); Console.WriteLine("Last Name: {0}", myTable.Rows[0][1]); Console.WriteLine("Age : {0}", myTable.Rows[0][2]);
}

Do be aware that you’ll take a rather deep dive into ADO.NET beginning with Chapter 20, so if some of the previous code seems unfamiliar, fear not. The main point of this example is that indexer methods can support multiple dimensions and, if used correctly, can simplify the way you interact with contained subobjects in custom collections.

Indexer Definitions on Interface Types
Indexers can be defined on a given .NET interface type to allow supporting types to provide a custom implementation. Here is a simple example of an interface that defines a protocol for obtaining string objects using a numerical indexer:

public interface IStringContainer
{
string this[int index] { get; set; }
}

With this interface definition, any class or structure that implements this interface must now support a read-write indexer that manipulates subitems using a numerical value. Here is a partial implementation of such a class:

class SomeClass : IStringContainer
{
private List myStrings = new List();

public string this[int index]
{
get => myStrings[index];
set => myStrings.Insert(index, value);
}
}

That wraps up the first major topic of this chapter. Now let’s examine a language feature that lets you build custom classes or structures that respond uniquely to the intrinsic operators of C#. Next, allow me to introduce the concept of operator overloading.

Understanding Operator Overloading
C#, like any programming language, has a canned set of tokens that are used to perform basic operations on intrinsic types. For example, you know that the + operator can be applied to two integers to yield a larger integer.

// The + operator with ints. int a = 100;
int b = 240;
int c = a + b; // c is now 340

Once again, this is no major news flash, but have you ever stopped and noticed how the same + operator can be applied to most intrinsic C# data types? For example, consider this code:

// + operator with strings.

string s1 = "Hello"; string s2 = " world!";
string s3 = s1 + s2; // s3 is now "Hello World!"

The + operator functions in specific ways based on the supplied data types (strings or integers, in this case). When the + operator is applied to numerical types, the result is the summation of the operands.
However, when the + operator is applied to string types, the result is string concatenation.
The C# language gives you the capability to build custom classes and structures that also respond uniquely to the same set of basic tokens (such as the + operator). While not every possible C# operator can be overloaded, many can, as shown in Table 11-1.

Table 11-1. Overloadability of C# Operators

C# Operator Overloadability
+, -, !, ~, ++, –, true,
false These unary operators can be overloaded. C# demands that if true or false is overloaded, both must be overloaded.
+, -, *, /, %, &, |, ^, <<,

These binary operators can be overloaded.
==,!=, <, >, <=, >= These comparison operators can be overloaded. C# demands that “like” operators (i.e., < and >, <= and >=, == and !=) are overloaded together.
[] The [] operator cannot be overloaded. As you saw earlier in this chapter, however, the indexer construct provides the same functionality.
() The () operator cannot be overloaded. As you will see later in this chapter, however, custom conversion methods provide the same functionality.
+=, -=, *=, /=, %=, &=,
|=, ^=, <<=, >>= Shorthand assignment operators cannot be overloaded; however, you receive them as a freebie when you overload the related binary operator.

Overloading Binary Operators
To illustrate the process of overloading binary operators, assume the following simple Point class is defined in a new Console Application project named OverloadedOps:

namespace OverloadedOps;
// Just a simple, everyday C# class. public class Point
{
public int X {get; set;} public int Y {get; set;}

public Point(int xPos, int yPos)
{
X = xPos;
Y = yPos;
}
public override string ToString()
=> $"[{this.X}, {this.Y}]";

}

Now, logically speaking, it makes sense to “add” Points together. For example, if you added together two Point variables, you should receive a new Point that is the summation of the X and Y values. Of course, it might also be helpful to subtract one Point from another. Ideally, you would like to be able to author the following code:

using OverloadedOps;

// Adding and subtracting two points?
Console.WriteLine(" Fun with Overloaded Operators \n");

// Make two points.
Point ptOne = new Point(100, 100); Point ptTwo = new Point(40, 40);
Console.WriteLine("ptOne = {0}", ptOne); Console.WriteLine("ptTwo = {0}", ptTwo);
// Add the points to make a bigger point? Console.WriteLine("ptOne + ptTwo: {0} ", ptOne + ptTwo);

// Subtract the points to make a smaller point? Console.WriteLine("ptOne – ptTwo: {0} ", ptOne – ptTwo); Console.ReadLine();

However, as your Point now stands, you will receive compile-time errors, as the Point type does not know how to respond to the + or – operator. To equip a custom type to respond uniquely to intrinsic operators, C# provides the operator keyword, which you can use only in conjunction with the static keyword. When you overload a binary operator (such as + and -), you will most often pass in two arguments that are the same type as the defining class (a Point in this example), as illustrated in the following code update:

// A more intelligent Point type. public class Point
{

// Overloaded operator +.
public static Point operator + (Point p1, Point p2)
=> new Point(p1.X + p2.X, p1.Y + p2.Y);

// Overloaded operator -.
public static Point operator – (Point p1, Point p2)
=> new Point(p1.X – p2.X, p1.Y – p2.Y);
}

The logic behind operator + is simply to return a new Point object based on the summation of the fields of the incoming Point parameters. Thus, when you write pt1 + pt2, under the hood you can envision the following hidden call to the static operator + method:

// Pseudo-code: Point p3 = Point.operator+ (p1, p2) Point p3 = p1 + p2;

Likewise, p1–p2 maps to the following:

// Pseudo-code: Point p4 = Point.operator- (p1, p2) Point p4 = p1 – p2;

With this update, your program now compiles, and you find you can add and subtract Point objects, as shown in the following output:

Fun with Overloaded Operators ptOne = [100, 100]
ptTwo = [40, 40]
ptOne + ptTwo: [140, 140]
ptOne – ptTwo: [60, 60]

When you are overloading a binary operator, you are not required to pass in two parameters of the same type. If it makes sense to do so, one of the arguments can differ. For example, here is an overloaded operator

  • that allows the caller to obtain a new Point that is based on a numerical adjustment:

public class Point
{

public static Point operator + (Point p1, int change)
=> new Point(p1.X + change, p1.Y + change);

public static Point operator + (int change, Point p1)
=> new Point(p1.X + change, p1.Y + change);
}

Notice that you need both versions of the method if you want the arguments to be passed in either order (i.e., you can’t just define one of the methods and expect the compiler to automatically support the other one). You are now able to use these new versions of operator + as follows:

// Prints [110, 110].
Point biggerPoint = ptOne + 10; Console.WriteLine("ptOne + 10 = {0}", biggerPoint);

// Prints [120, 120].
Console.WriteLine("10 + biggerPoint = {0}", 10 + biggerPoint); Console.WriteLine();

What of the += and –= Operators?
If you are coming to C# from a C++ background, you might lament the loss of overloading the shorthand assignment operators (+=, -=, etc.). Don’t despair. In terms of C#, the shorthand assignment operators are automatically simulated if a type overloads the related binary operator. Thus, given that the Point structure has already overloaded the + and – operators, you can write the following:

// Overloading binary operators results in a freebie shorthand operator.

// Freebie +=
Point ptThree = new Point(90, 5);

Console.WriteLine("ptThree = {0}", ptThree); Console.WriteLine("ptThree += ptTwo: {0}", ptThree += ptTwo);

// Freebie -=
Point ptFour = new Point(0, 500); Console.WriteLine("ptFour = {0}", ptFour);
Console.WriteLine("ptFour -= ptThree: {0}", ptFour -= ptThree); Console.ReadLine();

Overloading Unary Operators
C# also allows you to overload various unary operators, such as ++ and –. When you overload a unary operator, you also must use the static keyword with the operator keyword; however, in this case, you simply pass in a single parameter that is the same type as the defining class/structure. For example, if you were to update the Point with the following overloaded operators:

public class Point
{

// Add 1 to the X/Y values for the incoming Point. public static Point operator ++(Point p1)
=> new Point(p1.X+1, p1.Y+1);

// Subtract 1 from the X/Y values for the incoming Point. public static Point operator –(Point p1)
=> new Point(p1.X-1, p1.Y-1);
}

you could increment and decrement Point’s x and y values like this:


// Applying the ++ and — unary operators to a Point. Point ptFive = new Point(1, 1); Console.WriteLine("++ptFive = {0}", ++ptFive); // [2, 2]
Console.WriteLine("–ptFive = {0}", –ptFive); // [1, 1]

// Apply same operators as postincrement/decrement. Point ptSix = new Point(20, 20);
Console.WriteLine("ptSix++ = {0}", ptSix++); // [20, 20]
Console.WriteLine("ptSix– = {0}", ptSix–); // [21, 21] Console.ReadLine();

Notice in the preceding code example you are applying the custom ++ and — operators in two different manners. In C++, it is possible to overload pre- and post-increment/decrement operators separately. This
is not possible in C#. However, the return value of the increment/decrement is automatically handled

“correctly” free of charge (i.e., for an overloaded ++ operator, pt++ has the value of the unmodified object as its value within an expression, while ++pt has the new value applied before use in the expression).

Overloading Equality Operators
As you might recall from Chapter 6, System.Object.Equals() can be overridden to perform value-based (rather than referenced-based) comparisons between reference types. If you choose to override Equals() (and the often-related System.Object.GetHashCode() method), it is trivial to overload the equality operators (== and !=). To illustrate, here is the updated Point type:

// This incarnation of Point also overloads the == and != operators. public class Point
{

public override bool Equals(object o)
=> o.ToString() == this.ToString();

public override int GetHashCode()
=> this.ToString().GetHashCode();

// Now let’s overload the == and != operators. public static bool operator ==(Point p1, Point p2)
=> p1.Equals(p2);

public static bool operator !=(Point p1, Point p2)
=> !p1.Equals(p2);
}

Notice how the implementation of operator == and operator != simply makes a call to the overridden Equals() method to get the bulk of the work done. Given this, you can now exercise your Point class as follows:

// Make use of the overloaded equality operators.

Console.WriteLine("ptOne == ptTwo : {0}", ptOne == ptTwo); Console.WriteLine("ptOne != ptTwo : {0}", ptOne != ptTwo); Console.ReadLine();

As you can see, it is quite intuitive to compare two objects using the well-known == and != operators, rather than making a call to Object.Equals(). If you do overload the equality operators for a given class, keep in mind that C# demands that if you override the == operator, you must also override the != operator (if you forget, the compiler will let you know).

Overloading Comparison Operators
In Chapter 8, you learned how to implement the IComparable interface to compare the relationship between two like objects. You can, in fact, also overload the comparison operators (<, >, <=, and >=) for the same class.

As with the equality operators, C# demands that if you overload <, you must also overload >. The same holds true for the <= and >= operators. If the Point type overloaded these comparison operators, the object user could now compare Points, as follows:

// Using the overloaded < and > operators.

Console.WriteLine("ptOne < ptTwo : {0}", ptOne < ptTwo); Console.WriteLine("ptOne > ptTwo : {0}", ptOne > ptTwo); Console.ReadLine();

Assuming you have implemented the IComparable interface (or better yet, the generic equivalent), overloading the comparison operators is trivial. Here is the updated class definition:

// Point is also comparable using the comparison operators. public class Point : IComparable
{

public int CompareTo(Point other)
{
if (this.X > other.X && this.Y > other.Y)
{
return 1;
}
if (this.X < other.X && this.Y < other.Y)
{
return -1;
}
return 0;
}
public static bool operator <(Point p1, Point p2)
=> p1.CompareTo(p2) < 0;

public static bool operator >(Point p1, Point p2)
=> p1.CompareTo(p2) > 0;

public static bool operator <=(Point p1, Point p2)
=> p1.CompareTo(p2) <= 0;

public static bool operator >=(Point p1, Point p2)
=> p1.CompareTo(p2) >= 0;
}

Final Thoughts Regarding Operator Overloading
As you have seen, C# provides the capability to build types that can respond uniquely to various intrinsic, well-known operators. Now, before you go and retrofit all your classes to support such behavior, you must be sure that the operators you are about to overload make some sort of logical sense in the world at large.
For example, let’s say you overloaded the multiplication operator for the MiniVan class. What exactly would it mean to multiply two MiniVan objects? Not much. In fact, it would be confusing for teammates to see the following use of MiniVan objects:

// Huh?! This is far from intuitive… MiniVan newVan = myVan * yourVan;

Overloading operators is generally useful only when you’re building atomic data types. Vectors, matrices, text, points, shapes, sets, etc., make good candidates for operator overloading. People, managers, cars, database connections, and web pages do not. As a rule of thumb, if an overloaded operator makes it harder for the user to understand a type’s functionality, don’t do it. Use this feature wisely.

Understanding Custom Type Conversions
Let’s now examine a topic closely related to operator overloading: custom type conversions. To set the stage for the discussion, let’s quickly review the notion of explicit and implicit conversions between numerical data and related class types.

Recall: Numerical Conversions
In terms of the intrinsic numerical types (sbyte, int, float, etc.), an explicit conversion is required when you attempt to store a larger value in a smaller container, as this could result in a loss of data. Basically, this is your way to tell the compiler, “Leave me alone, I know what I am trying to do.” Conversely, an implicit conversion happens automatically when you attempt to place a smaller type in a destination type that will not result in a loss of data.

int a = 123;
long b = a; // Implicit conversion from int to long. int c = (int) b; // Explicit conversion from long to int.

Recall: Conversions Among Related Class Types
As shown in Chapter 6, class types may be related by classical inheritance (the “is-a” relationship). In this case, the C# conversion process allows you to cast up and down the class hierarchy. For example, a derived class can always be implicitly cast to a base type. However, if you want to store a base class type in a derived variable, you must perform an explicit cast, like so:

// Two related class types. class Base{}
class Derived : Base{}

// Implicit cast between derived to base. Base myBaseType;
myBaseType = new Derived();
// Must explicitly cast to store base reference
// in derived type.
Derived myDerivedType = (Derived)myBaseType;

This explicit cast works because the Base and Derived classes are related by classical inheritance and
myBaseType is constructed as an instance of Derived. However, if myBaseType is an instance of Base, the cast

throws an InvalidCastException. If there is any doubt that the cast will fail, you should use the as keyword, as discussed in Chapter 6. Here is the sample reworked to demonstrate this:

// Implicit cast between derived to base. Base myBaseType2 = new();
// Throws InvalidCastException
//Derived myDerivedType2 = (Derived)myBaseType2 as Derived;
//No exception, myDerivedType2 is null
Derived myDerivedType2 = myBaseType2 as Derived;

However, what if you have two class types in different hierarchies with no common parent (other than System.Object) that require conversions? Given that they are not related by classical inheritance, typical casting operations offer no help (and you would get a compiler error to boot!).
On a related note, consider value types (structures). Assume you have two structures named Square and Rectangle. Given that structures cannot leverage classic inheritance (as they are always sealed), you have no natural way to cast between these seemingly related types.
While you could create helper methods in the structures (such as Rectangle.ToSquare()), C# lets you build custom conversion routines that allow your types to respond to the () casting operator. Therefore, if you configured the structures correctly, you would be able to use the following syntax to explicitly convert between them as follows:

// Convert a Rectangle to a Square! Rectangle rect = new Rectangle
{
Width = 3;
Height = 10;
}
Square sq = (Square)rect;

Creating Custom Conversion Routines
Begin by creating a new Console Application project named CustomConversions. C# provides two keywords, explicit and implicit, that you can use to control how your types respond during an attempted conversion. Assume you have the following structure definitions:

namespace CustomConversions; public struct Rectangle
{
public int Width {get; set;} public int Height {get; set;}

public Rectangle(int w, int h)
{
Width = w;
Height = h;
}

public void Draw()
{

for (int i = 0; i < Height; i++)
{
for (int j = 0; j < Width; j++)
{
Console.Write("*");
}
Console.WriteLine();
}
}

public override string ToString()
=> $"[Width = {Width}; Height = {Height}]";
}

namespace CustomConversions; public struct Square
{
public int Length {get; set;} public Square(int l) : this()
{
Length = l;
}

public void Draw()
{
for (int i = 0; i < Length; i++)
{
for (int j = 0; j < Length; j++)
{
Console.Write("*");
}
Console.WriteLine();
}
}

public override string ToString() => $"[Length = {Length}]";

// Rectangles can be explicitly converted into Squares. public static explicit operator Square(Rectangle r)
{
Square s = new Square {Length = r.Height}; return s;
}
}
}

Notice that this iteration of the Square type defines an explicit conversion operator. Like the process of overloading an operator, conversion routines make use of the C# operator keyword, in conjunction with the explicit or implicit keyword, and must be defined as static. The incoming parameter is the entity you are converting from, while the operator type is the entity you are converting to.

In this case, the assumption is that a square (being a geometric pattern in which all sides are of equal length) can be obtained from the height of a rectangle. Thus, you are free to convert a Rectangle into a Square, as follows:

using CustomConversions;

Console.WriteLine(" Fun with Conversions \n");
// Make a Rectangle.
Rectangle r = new Rectangle(15, 4); Console.WriteLine(r.ToString()); r.Draw();

Console.WriteLine();

// Convert r into a Square,
// based on the height of the Rectangle. Square s = (Square)r; Console.WriteLine(s.ToString()); s.Draw();
Console.ReadLine();

You can see the output here:

Fun with Conversions [Width = 15; Height = 4]





[Length = 4]





While it may not be all that helpful to convert a Rectangle into a Square within the same scope, assume you have a function that has been designed to take Square parameters.

// This method requires a Square type. static void DrawSquare(Square sq)
{
Console.WriteLine(sq.ToString()); sq.Draw();
}

Using your explicit conversion operation on the Square type, you can now pass in Rectangle types for processing using an explicit cast, like so:

// Convert Rectangle to Square to invoke method. Rectangle rect = new Rectangle(10, 5); DrawSquare((Square)rect);
Console.ReadLine();

Additional Explicit Conversions for the Square Type
Now that you can explicitly convert Rectangles into Squares, let’s examine a few additional explicit conversions. Given that a square is symmetrical on all sides, it might be helpful to provide an explicit conversion routine that allows the caller to cast from an integer type into a Square (which, of course, will have a side length equal to the incoming integer). Likewise, what if you were to update Square such that the caller can cast from a Square into an int? Here is the calling logic:


// Converting an int to a Square. Square sq2 = (Square)90; Console.WriteLine("sq2 = {0}", sq2);

// Converting a Square to an int. int side = (int)sq2;
Console.WriteLine("Side length of sq2 = {0}", side); Console.ReadLine();

and here is the update to the Square class:

public struct Square
{

public static explicit operator Square(int sideLength)
{
Square newSq = new Square {Length = sideLength}; return newSq;
}

public static explicit operator int (Square s) => s.Length;
}

To be honest, converting from a Square into an integer may not be the most intuitive (or useful) operation (after all, chances are you could just pass such values to a constructor). However, it does point out an important fact regarding custom conversion routines: the compiler does not care what you convert to or from, if you have written syntactically correct code.
Thus, as with overloading operators, just because you can create an explicit cast operation for a given type does not mean you should. Typically, this technique will be most helpful when you’re creating structure types, given that they are unable to participate in classical inheritance (where casting comes for free).

Defining Implicit Conversion Routines
So far, you have created various custom explicit conversion operations. However, what about the following
implicit conversion?


Square s3 = new Square {Length = 83};

// Attempt to make an implicit cast? Rectangle rect2 = s3;

Console.ReadLine();

This code will not compile, given that you have not provided an implicit conversion routine for the Rectangle type. Now here is the catch: it is illegal to define explicit and implicit conversion functions on the same type if they do not differ by their return type or parameter set. This might seem like a limitation; however, the second catch is that when a type defines an implicit conversion routine, it is legal for the caller to make use of the explicit cast syntax!
Confused? To clear things up, let’s add an implicit conversion routine to the Rectangle structure using the C# implicit keyword (note that the following code assumes the width of the resulting Rectangle is computed by multiplying the side of the Square by 2):

public struct Rectangle
{

public static implicit operator Rectangle(Square s)
{
Rectangle r = new Rectangle
{
Height = s.Length,
Width = s.Length * 2 // Assume the length of the new Rectangle with (Length x 2).
};
return r;
}
}

With this update, you are now able to convert between types, as follows:


// Implicit cast OK!
Square s3 = new Square { Length= 7};

Rectangle rect2 = s3; Console.WriteLine("rect2 = {0}", rect2);

// Explicit cast syntax still OK! Square s4 = new Square {Length = 3}; Rectangle rect3 = (Rectangle)s4;

Console.WriteLine("rect3 = {0}", rect3); Console.ReadLine();

That wraps up your look at defining custom conversion routines. As with overloaded operators, remember that this bit of syntax is simply a shorthand notation for “normal” member functions, and in this light, it is always optional. When used correctly, however, custom structures can be used more naturally, as they can be treated as true class types related by inheritance.

Understanding Extension Methods
.NET 3.5 introduced the concept of extension methods, which allow you to add new methods or properties to a class or structure, without modifying the original type in any direct manner. So, where might this be helpful? Consider the following possibilities.
First, say you have a given class that is in production. It becomes clear over time that this class should support a handful of new members. If you modify the current class definition directly, you risk the possibility of breaking backward compatibility with older code bases making use of it, as they might not have been compiled with the latest and greatest class definition. One way to ensure backward compatibility is to create a new derived class from the existing parent; however, now you have two classes to maintain. As we all know, code maintenance is the least glamorous part of a software engineer’s job description.
Now consider this situation. Let’s say you have a structure (or maybe a sealed class) and want to add new members so that it behaves polymorphically in your system. Since structures and sealed classes cannot be extended, your only choice is to add the members to the type, once again risking breaking backward compatibility!
Using extension methods, you can modify types without subclassing and without modifying the type directly. The catch is that the new functionality is offered to a type only if the extension methods have been referenced for use in your current project.

Defining Extension Methods
When you define extension methods, the first restriction is that they must be defined within a static class (see Chapter 5); therefore, each extension method must be declared with the static keyword. The second point is that all extension methods are marked as such by using the this keyword as a modifier on the first (and only the first) parameter of the method in question. The “this qualified” parameter represents the item being extended.
To illustrate, create a new Console Application project named ExtensionMethods. Now, assume you are authoring a class named MyExtensions that defines two extension methods. The first method allows any object to use a new method named DisplayDefiningAssembly() that makes use of types in the System.
Reflection namespace to display the name of the assembly containing the type in question.

■Note You will formally examine the reflection apI in Chapter 17. If you are new to the topic, simply understand that reflection allows you to discover the structure of assemblies, types, and type members at runtime.

The second extension method, named ReverseDigits(), allows any int to obtain a new version of itself where the value is reversed digit by digit. For example, if an integer with the value 1234 called
ReverseDigits(), the integer returned is set to the value 4321. Consider the following class implementation (be sure to import the System.Reflection namespace if you are following along):

using System.Reflection;

namespace MyExtensionMethods; static class MyExtensions
{
// This method allows any object to display the assembly
// it is defined in.
public static void DisplayDefiningAssembly(this object obj)

{
Console.WriteLine("{0} lives here: => {1}\n", obj.GetType().Name, Assembly.GetAssembly(obj.GetType()).GetName().Name);
}

// This method allows any integer to reverse its digits.
// For example, 56 would return 65.
public static int ReverseDigits(this int i)
{
// Translate int into a string, and then
// get all the characters.
char[] digits = i.ToString().ToCharArray();

// Now reverse items in the array. Array.Reverse(digits);

// Put back into string.
string newDigits = new string(digits);

// Finally, return the modified string back as an int. return int.Parse(newDigits);
}
}

Again, note how the first parameter of each extension method has been qualified with the this keyword, before defining the parameter type. It is always the case that the first parameter of an extension method represents the type being extended. Given that DisplayDefiningAssembly() has been prototyped to extend System.Object, every type now has this new member, as Object is the parent to all types in the
.NET platform. However, ReverseDigits() has been prototyped to extend only integer types; therefore, if anything other than an integer attempts to invoke this method, you will receive a compile-time error.

■ Note understand that a given extension method can have multiple parameters, but only the first parameter can be qualified with this. the additional parameters would be treated as normal incoming parameters for use by the method.

Invoking Extension Methods
Now that you have these extension methods in place, consider the following code example that applies the extension method to various types in the base class libraries:

using MyExtensionMethods;

Console.WriteLine(" Fun with Extension Methods \n");

// The int has assumed a new identity! int myInt = 12345678;

myInt.DisplayDefiningAssembly();

// So has the DataSet!
System.Data.DataSet d = new System.Data.DataSet(); d.DisplayDefiningAssembly();

// Use new integer functionality. Console.WriteLine("Value of myInt: {0}", myInt); Console.WriteLine("Reversed digits of myInt: {0}",
myInt.ReverseDigits()); Console.ReadLine();
Here is the output:

Fun with Extension Methods Int32 lives here: => System.Private.CoreLib
DataSet lives here: => System.Data.Common Value of myInt: 12345678
Reversed digits of myInt: 87654321

Importing Extension Methods
When you define a class containing extension methods, it will no doubt be defined within a namespace. If this namespace is different from the namespace using the extension methods, you will need to make use of the expected C# using keyword. When you do, your code file has access to all extension methods for the type being extended. This is important to remember because if you do not explicitly import the correct namespace, the extension methods are not available for that C# code file.
In effect, although it can appear on the surface that extension methods are global in nature, they are in fact limited to the namespaces that define them or the namespaces that import them. Recall that you wrapped the MyExtensions class into a namespace named MyExtensionMethods, as follows:

namespace MyExtensionMethods; static class MyExtensions
{

}

To use the extension methods in the class, you need to explicitly import the MyExtensionMethods
namespace, as we did in the top-level statements used to exercise the examples.

Extending Types Implementing Specific Interfaces
At this point, you have seen how to extend classes (and, indirectly, structures that follow the same syntax) with new functionality via extension methods. It is also possible to define an extension method that can only extend a class or structure that implements the correct interface. For example, you could say something
to the effect of “If a class or structure implements IEnumerable, then that type gets the following new members.” Of course, it is possible to demand that a type support any interface at all, including your own custom interfaces.
To illustrate, create a new Console Application project named InterfaceExtensions. The goal here is to add a new method to any type that implements IEnumerable, which would include any array and many
nongeneric collection classes (recall from Chapter 10 that the generic IEnumerable interface extends the nongeneric IEnumerable interface). Add the following extension class to your new project:

namespace InterfaceExtensions; static class AnnoyingExtensions
{
public static void PrintDataAndBeep(
this System.Collections.IEnumerable iterator)
{
foreach (var item in iterator)
{
Console.WriteLine(item); Console.Beep();
}
}
}

Given that the PrintDataAndBeep() method can be used by any class or structure that implements
IEnumerable, you could test via the following code:

using InterfaceExtensions;

Console.WriteLine(" Extending Interface Compatible Types \n");

// System.Array implements IEnumerable! string[] data =
{ "Wow", "this", "is", "sort", "of", "annoying",
"but", "in", "a", "weird", "way", "fun!"}; data.PrintDataAndBeep();

Console.WriteLine();

// List implements IEnumerable!
List myInts = new List() {10, 15, 20}; myInts.PrintDataAndBeep();

Console.ReadLine();

That wraps up your examination of C# extension methods. Remember that this language feature can be useful whenever you want to extend the functionality of a type but do not want to subclass (or cannot subclass if the type is sealed), for the purposes of polymorphism. As you will see later in the text, extension methods play a key role for LINQ APIs. In fact, you will see that under the LINQ APIs, one of the most common items being extended is a class or structure implementing (surprise!) the generic version of IEnumerable.

Extension Method GetEnumerator Support (New 9.0)
Prior to C# 9.0, to use foreach on a class, the GetEnumerator() method had to be defined on that class directly. With C# 9.0, the foreach method will examine extension methods on the class and, if a
GetEnumerator() method is found, will use that method to get the IEnumerator for that class. To see this in action, add a new Console application named ForEachWithExtensionMethods, and add simplified versions of the Car and Garage classes from Chapter 8.

//Car.cs
namespace ForEachWithExtensionMethods; class Car
{
// Car properties.
public int CurrentSpeed {get; set;} = 0; public string PetName {get; set;} = "";

// Constructors. public Car() {}
public Car(string name, int speed)
{
CurrentSpeed = speed;
PetName = name;
}

// See if Car has overheated.
}

//Garage.cs
namespace ForEachWithExtensionMethods; class Garage
{
public Car[] CarsInGarage { get; set; }

// Fill with some Car objects upon startup. public Garage()
{
CarsInGarage = new Car[4]; CarsInGarage[0] = new Car("Rusty", 30); CarsInGarage[1] = new Car("Clunker", 55); CarsInGarage[2] = new Car("Zippy", 30); CarsInGarage[3] = new Car("Fred", 30);
}

}

Note that the Garage class does not implement IEnumerable, nor does it have a GetEnumerator()
method. The GetEnumerator() method is added through the GarageExtensions class, shown here:

using System.Collections;

namespace ForEachWithExtensionMethods; static class GarageExtensions
{
public static IEnumerator GetEnumerator(this Garage g)
=> g.CarsInGarage.GetEnumerator();
}

The code to test this new feature is the same code used to test the GetEnumerator() method in Chapter 8.
Update Program.cs to the following:

using ForEachWithExtensionMethods;

Console.WriteLine(" Support for Extension Method GetEnumerator \n"); Garage carLot = new Garage();

// Hand over each car in the collection? foreach (Car c in carLot)
{
Console.WriteLine("{0} is going {1} MPH", c.PetName, c.CurrentSpeed);
}

You will see that the code works, printing to the console the list of cars and their speed.
Support for Extension Method GetEnumerator Rusty is going 30 MPH
Clunker is going 55 MPH Zippy is going 30 MPH Fred is going 30 MPH

■ Note there is a potential drawback to this new feature, in that classes that were never meant to be
foreached could now be foreached.

Understanding Anonymous Types
As an object-oriented programmer, you know the benefits of defining classes to represent the state and functionality of a given item you are attempting to model. To be sure, whenever you need to define a class that is intended to be reused across projects and that provides numerous bits of functionality through a set of methods, events, properties, and custom constructors, creating a new C# class is common practice.

However, there are other times when you want to define a class simply to model a set of encapsulated (and somehow related) data points without any associated methods, events, or other specialized functionality. Furthermore, what if this type is to be used by only a handful of methods in your program? It would be rather a bother to define a full class definition as shown next when you know full well this class will be used in only a handful of places. To accentuate this point, here is the rough outline of what you might need to do when you need to create a “simple” data type that follows typical value-based semantics:

class SomeClass
{
// Define a set of private member variables…

// Make a property for each member variable…

// Override ToString() to account for key member variables…

// Override GetHashCode() and Equals() to work with value-based equality…
}

As you can see, it is not necessarily so simple. Not only do you need to author a fair amount of code, but you have another class to maintain in your system. For temporary data such as this, it would be useful to whip up a custom data type on the fly. For example, let’s say you need to build a custom method that
receives a set of incoming parameters. You would like to take these parameters and use them to create a new data type for use in this method scope. Further, you would like to quickly print out this data using the typical ToString() method and perhaps use other members of System.Object. You can do this very thing using anonymous type syntax.

Defining an Anonymous Type
When you define an anonymous type, you do so by using the var keyword (see Chapter 3) in conjunction with object initialization syntax (see Chapter 5). You must use the var keyword because the compiler will automatically generate a new class definition at compile time (and you never see the name of this class in your C# code). The initialization syntax is used to tell the compiler to create private backing fields and (read- only) properties for the newly created type.
To illustrate, create a new Console Application project named AnonymousTypes. Now, add the following method to your Program.cs file, which composes a new type, on the fly, using the incoming parameter data:

static void BuildAnonymousType( string make, string color, int currSp )
{
// Build anonymous type using incoming args.
var car = new { Make = make, Color = color, Speed = currSp };

// Note you can now use this type to get the property data!
Console.WriteLine("You have a {0} {1} going {2} MPH", car.Color, car.Make, car.Speed);

// Anonymous types have custom implementations of each virtual
// method of System.Object. For example: Console.WriteLine("ToString() == {0}", car.ToString());
}

Note that an anonymous type can also be created inline in addition to wrapping the code in a function, as shown here:

Console.WriteLine(" Fun with Anonymous Types \n");

// Make an anonymous type representing a car.
var myCar = new { Color = "Bright Pink", Make = "Saab", CurrentSpeed = 55 };

// Now show the color and make.
Console.WriteLine("My car is a {0} {1}.", myCar.Color, myCar.Make);

// Now call our helper method to build anonymous type via args. BuildAnonymousType("BMW", "Black", 90);

Console.ReadLine();

So, at this point, simply understand that anonymous types allow you to quickly model the “shape” of data with little overhead. This technique is little more than a way to whip up a new data type on the fly,
which supports bare-bones encapsulation via properties and acts according to value-based semantics. To understand that last point, let’s see how the C# compiler builds out anonymous types at compile time and, specifically, how it overrides the members of System.Object.

The Internal Representation of Anonymous Types
All anonymous types are automatically derived from System.Object and, therefore, support each of the members provided by this base class. Given this, you could invoke ToString(), GetHashCode(), Equals(), or GetType() on the implicitly typed myCar object. Assume your Program.cs file defines the following static helper function:

static void ReflectOverAnonymousType(object obj)
{
Console.WriteLine("obj is an instance of: {0}", obj.GetType().Name);
Console.WriteLine("Base class of {0} is {1}", obj.GetType().Name, obj.GetType().BaseType);
Console.WriteLine("obj.ToString() == {0}", obj.ToString()); Console.WriteLine("obj.GetHashCode() == {0}",
obj.GetHashCode());
Console.WriteLine();
}

Now assume you invoke this method, passing in the myCar object as the parameter, like so:

Console.WriteLine(" Fun with Anonymous Types \n");

// Make an anonymous type representing a car.
var myCar = new {Color = "Bright Pink", Make = "Saab",

CurrentSpeed = 55};

// Reflect over what the compiler generated. ReflectOverAnonymousType(myCar);

Console.ReadLine();

The output will look like the following:

Fun with Anonymous Types
obj is an instance of: <>f AnonymousType03 Base class of <>f AnonymousType03 is System.Object
obj.ToString() = { Color = Bright Pink, Make = Saab, CurrentSpeed = 55 } obj.GetHashCode() = -564053045

First, notice that, in this example, the myCar object is of type <>f AnonymousType0`3 (your name may differ). Remember that the assigned type name is completely determined by the compiler and is not directly accessible in your C# code base.
Perhaps most important, notice that each name-value pair defined using the object initialization syntax is mapped to an identically named read-only property and a corresponding private init-only backing field. The following C# code approximates the compiler-generated class used to represent the myCar object (which again can be verified using ildasm.exe):

.class private sealed ‘<>f AnonymousType0’3′<‘j TPar’, ‘j TPar’, j TPar>’
extends [System.Runtime][System.Object]
{
// init-only fields.
private initonly j TPar i Field;
private initonly j TPar i Field; private initonly j TPar i Field;

// Default constructor.
public <>f AnonymousType0(j TPar Color,

j TPar Make, j TPar CurrentSpeed);
// Overridden methods.
public override bool Equals(object value); public override int GetHashCode();
public override string ToString();

// Read-only properties.
j TPar Color { get; }
j TPar CurrentSpeed { get; }
j TPar Make { get; }
}

The Implementation of ToString() and GetHashCode()
All anonymous types automatically derive from System.Object and are provided with an overridden version of Equals(), GetHashCode(), and ToString(). The ToString() implementation simply builds a string from each name-value pair. Here’s an example:

public override string ToString()
{
StringBuilder builder = new StringBuilder(); builder.Append(“{ Color = “); builder.Append(this.i Field); builder.Append(“, Make = “); builder.Append(this.i Field); builder.Append(“, CurrentSpeed = “); builder.Append(this.i Field); builder.Append(” }”);
return builder.ToString();
}

The GetHashCode() implementation computes a hash value using each anonymous type’s member variables as input to the System.Collections.Generic.EqualityComparer type. Using this implementation of GetHashCode(), two anonymous types will yield the same hash value if they have the
same set of properties that have been assigned the same values. Given this implementation, anonymous types are well suited to be contained within a Hashtable container.

The Semantics of Equality for Anonymous Types
While the implementation of the overridden ToString() and GetHashCode() methods is straightforward, you might be wondering how the Equals() method has been implemented. For example, if you were to define two “anonymous cars” variables that specify the same name-value pairs, would these two variables be considered equal? To see the results firsthand, update your Program.cs class with the following
new method:

static void EqualityTest()
{
// Make 2 anonymous classes with identical name/value pairs. var firstCar = new { Color = “Bright Pink”, Make = “Saab”,
CurrentSpeed = 55 };
var secondCar = new { Color = “Bright Pink”, Make = “Saab”, CurrentSpeed = 55 };

// Are they considered equal when using Equals()? if (firstCar.Equals(secondCar))
{
Console.WriteLine(“Same anonymous object!”);
}
else
{
Console.WriteLine(“Not the same anonymous object!”);
}

// Are they considered equal when using ==? if (firstCar == secondCar)
{
Console.WriteLine(“Same anonymous object!”);
}
else
{
Console.WriteLine(“Not the same anonymous object!”);
}

// Are these objects the same underlying type?
if (firstCar.GetType().Name == secondCar.GetType().Name)
{
Console.WriteLine(“We are both the same type!”);
}
else
{
Console.WriteLine(“We are different types!”);
}

// Show all the details. Console.WriteLine(); ReflectOverAnonymousType(firstCar); ReflectOverAnonymousType(secondCar);
}

When you call this method, the output might be somewhat surprising.

My car is a Bright Pink Saab.
You have a Black BMW going 90 MPH
ToString() == { Make = BMW, Color = Black, Speed = 90 }

Same anonymous object!
Not the same anonymous object! We are both the same type!

obj is an instance of: <>f AnonymousType0`3
Base class of <>f AnonymousType0`3 is System.Object
obj.ToString() == { Color = Bright Pink, Make = Saab, CurrentSpeed = 55 } obj.GetHashCode() == -925496951

obj is an instance of: <>f AnonymousType0`3
Base class of <>f AnonymousType0`3 is System.Object
obj.ToString() == { Color = Bright Pink, Make = Saab, CurrentSpeed = 55 } obj.GetHashCode() == -925496951

When you run this test code, you will see that the first conditional test where you call Equals() returns true and, therefore, the message “Same anonymous object!” prints out to the screen. This is because the compiler-generated Equals() method uses value-based semantics when testing for equality (e.g., checking the value of each field of the objects being compared).

However, the second conditional test, which makes use of the C# equality operator (==), prints out “Not the same anonymous object!” This might seem at first glance to be a bit counterintuitive. This result is because anonymous types do not receive overloaded versions of the C# equality operators (== and !=). Given this, when you test for equality of anonymous types using the C# equality operators (rather than the Equals() method), the references, not the values maintained by the objects, are being tested for equality.
Finally, in the final conditional test (where you examine the underlying type name), you find that the anonymous types are instances of the same compiler-generated class type (in this example,
<>f AnonymousType0`3) because firstCar and secondCar have the same properties (Color, Make, and
CurrentSpeed).
This illustrates an important but subtle point: the compiler will generate a new class definition only when an anonymous type contains unique names of the anonymous type. Thus, if you declare identical anonymous types (again, meaning the same names) within the same assembly, the compiler generates only a single anonymous type definition.

Anonymous Types Containing Anonymous Types
It is possible to create an anonymous type that is composed of other anonymous types. For example, assume you want to model a purchase order that consists of a timestamp, a price point, and the automobile purchased. Here is a new (slightly more sophisticated) anonymous type representing such an entity:

// Make an anonymous type that is composed of another. var purchaseItem = new {
TimeBought = DateTime.Now,
ItemBought = new {Color = “Red”, Make = “Saab”, CurrentSpeed = 55}, Price = 34.000};

ReflectOverAnonymousType(purchaseItem);

At this point, you should understand the syntax used to define anonymous types, but you might still be wondering exactly where (and when) to use this new language feature. To be blunt, anonymous type declarations should be used sparingly, typically only when making use of the LINQ technology set (see Chapter 13). You would never want to abandon the use of strongly typed classes/structures simply for the sake of doing so, given anonymous types’ numerous limitations, which include the following:
•You don’t control the name of the anonymous type.
•Anonymous types always extend System.Object.
•The fields and properties of an anonymous type are always read-only.
•Anonymous types cannot support events, custom methods, custom operators, or custom overrides.
•Anonymous types are always implicitly sealed.
•Anonymous types are always created using the default constructor.
However, when programming with the LINQ technology set, you will find that in many cases this syntax can be helpful when you want to quickly model the overall shape of an entity rather than its functionality.

Working with Pointer Types
And now for the final topic of the chapter, which most likely will be the least used of all C# features for most of your .NET projects.

■ Note In the examples that follow, I’m assuming you have some background in C++ pointer manipulation. If this is not true, feel free to skip this topic entirely. using pointers will not be a common task for most C# applications.

In Chapter 4, you learned that the .NET platform defines two major categories of data: value types and reference types. Truth be told, however, there is a third category: pointer types. To work with pointer types, you get specific operators and keywords that allow you to bypass the .NET Runtime’s memory management scheme and take matters into your own hands (see Table 11-2).

Table 11-2. Pointer-centric C# Operators and Keywords

Operator/Keyword Meaning in Life
* This operator is used to create a pointer variable (i.e., a variable that represents a direct location in memory). As in C++, this same operator is used for pointer indirection.
& This operator is used to obtain the address of a variable in memory.
-> This operator is used to access fields of a type that is represented by a pointer (the unsafe version of the C# dot operator).
[] This operator (in an unsafe context) allows you to index the slot pointed to by a pointer variable (if you’re a C++ programmer, you will recall the interplay between a pointer variable and the [] operator).
++, — In an unsafe context, the increment and decrement operators can be applied to pointer types.
+, – In an unsafe context, the addition and subtraction operators can be applied to pointer types.
==, !=, <, >, <=, => In an unsafe context, the comparison and equality operators can be applied to pointer types.
Stackalloc In an unsafe context, the stackalloc keyword can be used to allocate C# arrays directly on the stack.
Fixed In an unsafe context, the fixed keyword can be used to temporarily fix a variable so that its address can be found.

Now, before digging into the details, let me again point out that you will seldom if ever need to make use of pointer types. Although C# does allow you to drop down to the level of pointer manipulations, understand that the .NET runtime has absolutely no clue of your intentions. Thus, if you mismanage a pointer, you are the one in charge of dealing with the consequences. Given these warnings, when exactly would you need to work with pointer types? There are two common situations.

• You are looking to optimize select parts of your application by directly manipulating memory outside the management of the .NET 5 Runtime.
• You are calling methods of a C-based .dll or COM server that demand pointer types as parameters. Even in this case, you can often bypass pointer types in favor of the System.IntPtr type and members of the System.Runtime.InteropServices. Marshal type.
When you decide to make use of this C# language feature, you are required to inform the C# compiler of your intentions by enabling your project to support “unsafe code.” Create a new Console Application project named UnsafeCode, and set the project to support unsafe code by adding the following to the UnsafeCode. csproj file:


true

Visual Studio 2022 provides a GUI to set this property. Access your project’s property page, navigate to the Build tab, and then select the “Allow unsafe code” box. See Figure 11-1.

Figure 11-1. Enabling unsafe code using Visual Studio 2022

To select the build configuration for the settings, hover over the check box or to the left side of the “Unsafe code” label, and a gear will appear. Click the gear to select where the setting will be applied. See Figure 11-2.

Figure 11-2. Specifying the configuration(s) for the unsafe code setting

The unsafe Keyword
When you want to work with pointers in C#, you must specifically declare a block of “unsafe code” using the unsafe keyword (any code that is not marked with the unsafe keyword is considered “safe” automatically). For example, the following Program.cs file declares a scope of unsafe code within the safe top-level statements:

using UnsafeCode;
Console.WriteLine(“***** Calling method with unsafe code *****”); unsafe
{
// Work with pointer types here!
}
// Can’t work with pointers here!

In addition to declaring a scope of unsafe code within a method, you can build structures, classes, type members, and parameters that are “unsafe.” Here are a few examples to gnaw on (no need to define the Node or Node2 types in your current project):

// This entire structure is “unsafe” and can
// be used only in an unsafe context. unsafe struct Node
{

public int Value; public Node* Left; public Node* Right;
}

// This struct is safe, but the Node2* members
// are not. Technically, you may access “Value” from
// outside an unsafe context, but not “Left” and “Right”. public struct Node2
{
public int Value;

// These can be accessed only in an unsafe context! public unsafe Node2* Left;
public unsafe Node2* Right;
}

Methods (static or instance level) may be marked as unsafe as well. For example, assume you know that a static method will make use of pointer logic. To ensure that this method can be called only from an unsafe context, you could define the method as follows:

static unsafe void SquareIntPointer(int* myIntPointer)
{
// Square the value just for a test.
*myIntPointer *= *myIntPointer;
}

The configuration of your method demands that the caller invoke SquareIntPointer() as follows:

unsafe
{
int myInt = 10;

// OK, because we are in an unsafe context. SquareIntPointer(&myInt); Console.WriteLine(“myInt: {0}”, myInt);
}

int myInt2 = 5;

// Compiler error! Must be in unsafe context! SquareIntPointer(&myInt2); Console.WriteLine(“myInt: {0}”, myInt2);

If you would rather not force the caller to wrap the invocation within an unsafe context, you could wrap all the top-level statements with an unsafe block. If you are using a Main() method as entry point, you can update Main() with the unsafe keyword. In this case, the following code would compile:

static unsafe void Main(string[] args)
{
int myInt2 = 5;

SquareIntPointer(&myInt2); Console.WriteLine(“myInt: {0}”, myInt2);
}

If you run this version of the code, you will see the following output:

myInt: 25

■ Note It is important to note that the term unsafe was chosen for a reason. directly accessing the stack and working with pointers can cause unexpected issues with your application as well as the machine it is running on. If you have to work with unsafe code, be extra diligent.

Working with the * and & Operators
After you have established an unsafe context, you are then free to build pointers to data types using the
* operator and obtain the address of what is being pointed to using the & operator. Unlike in C or C++, in C# the * operator is applied to the underlying type only, not as a prefix to each pointer variable name. For example, consider the following code, which illustrates both the correct and incorrect ways to declare pointers to integer variables:

// No! This is incorrect under C#! int *pi, *pj;

// Yes! This is the way of C#. int* pi, pj;

Consider the following unsafe method:

static unsafe void PrintValueAndAddress()
{
int myInt;

// Define an int pointer, and
// assign it the address of myInt. int* ptrToMyInt = &myInt;

// Assign value of myInt using pointer indirection.
*ptrToMyInt = 123;

// Print some stats.
Console.WriteLine(“Value of myInt {0}”, myInt); Console.WriteLine(“Address of myInt {0:X}”, (int)&ptrToMyInt);
}

If you run this method from the unsafe block, you will see the following output:

**** Print Value And Address **** Value of myInt 123
Address of myInt 90F7E698

An Unsafe (and Safe) Swap Function
Of course, declaring pointers to local variables simply to assign their value (as in the previous example) is never required and not altogether useful. To illustrate a more practical example of unsafe code, assume you want to build a swap function using pointer arithmetic.

unsafe static void UnsafeSwap(int* i, int* j)
{
int temp = *i;
*i = *j;
*j = temp;
}

Very C-like, don’t you think? However, given your work previously, you should be aware that you could write the following safe version of your swap algorithm using the C# ref keyword:

static void SafeSwap(ref int i, ref int j)
{
int temp = i; i = j;
j = temp;
}

The functionality of each method is identical, thus reinforcing the point that direct pointer manipulation is not a mandatory task under C#. Here is the calling logic using safe top-level statements, with an unsafe context:

Console.WriteLine(“***** Calling method with unsafe code *****”);

// Values for swap. int i = 10, j = 20;

// Swap values “safely.”
Console.WriteLine(“\n***** Safe swap *****”); Console.WriteLine(“Values before safe swap: i = {0}, j = {1}”, i, j); SafeSwap(ref i, ref j);
Console.WriteLine(“Values after safe swap: i = {0}, j = {1}”, i, j);

// Swap values “unsafely.”
Console.WriteLine(“\n***** Unsafe swap *****”); Console.WriteLine(“Values before unsafe swap: i = {0}, j = {1}”, i, j); unsafe { UnsafeSwap(&i, &j); }

Console.WriteLine(“Values after unsafe swap: i = {0}, j = {1}”, i, j); Console.ReadLine();

Field Access via Pointers (the -> Operator)
Now assume you have defined a simple, safe Point structure, as follows:

struct Point
{
public int x; public int y;

public override string ToString() => $”({x}, {y})”;
}

If you declare a pointer to a Point type, you will need to make use of the pointer field-access operator (represented by ->) to access its public members. As shown in Table 11-2, this is the unsafe version of
the standard (safe) dot operator (.). In fact, using the pointer indirection operator (*), it is possible to dereference a pointer to (once again) apply the dot operator notation. Check out the unsafe method:

static unsafe void UsePointerToPoint()
{
// Access members via pointer. Point;
Point* p = &point; p->x = 100;
p->y = 200;
Console.WriteLine(p->ToString());

// Access members via pointer indirection. Point point2;
Point* p2 = &point2; (*p2).x = 100;
(*p2).y = 200;
Console.WriteLine((*p2).ToString());
}

The stackalloc Keyword
In an unsafe context, you may need to declare a local variable that allocates memory directly from the call stack (and is, therefore, not subject to .NET garbage collection). To do so, C# provides the stackalloc keyword, which is the C# equivalent to the _alloca function of the C runtime library. Here is a simple example:

static unsafe string UnsafeStackAlloc()
{
char* p = stackalloc char[52]; for (int k = 0; k < 52; k++) { p[k] = (char)(k + 65)k; } return new string(p); } Pinning a Type via the fixed Keyword As you saw in the previous example, allocating a chunk of memory within an unsafe context may be facilitated via the stackalloc keyword. By the very nature of this operation, the allocated memory is cleaned up as soon as the allocating method has returned (as the memory is acquired from the stack). However, assume a more complex example. During our examination of the -> operator, you created a value type named Point. Like all value types, the allocated memory is popped off the stack once the executing scope has terminated. For the sake of argument, assume Point was instead defined as a reference type, like so:

class PointRef // <= Renamed and retyped. { public int x; public int y; public override string ToString() => $”({x}, {y})”;
}

As you are aware, if the caller declares a variable of type Point, the memory is allocated on the garbage- collected heap. The burning question then becomes “What if an unsafe context wants to interact with this object (or any object on the heap)?” Given that garbage collection can occur at any moment, imagine the problems encountered when accessing the members of Point at the very point in time a sweep of the heap is underway. Theoretically, it is possible that the unsafe context is attempting to interact with a member that is no longer accessible or has been repositioned on the heap after surviving a generational sweep (which is an obvious problem).
To lock a reference type variable in memory from an unsafe context, C# provides the fixed keyword.
The fixed statement sets a pointer to a managed type and “pins” that variable during the execution of the code. Without fixed, pointers to managed variables would be of little use, since garbage collection could relocate the variables unpredictably. (In fact, the C# compiler will not allow you to set a pointer to a managed variable except in a fixed statement.)
Thus, if you create a PointRef object and want to interact with its members, you must write the following code (or receive a compiler error):

unsafe static void UseAndPinPoint()
{
PointRef pt = new PointRef
{
x = 5,
y = 6
};

// Pin pt in place so it will not
// be moved or GC-ed. fixed (int* p = &pt.x)
{
// Use int* variable here!
}

// pt is now unpinned, and ready to be GC-ed once
// the method completes. Console.WriteLine (“Point is: {0}”, pt);
}

In a nutshell, the fixed keyword allows you to build a statement that locks a reference variable in memory, such that its address remains constant for the duration of the statement (or scope block). Any time you interact with a reference type from within the context of unsafe code, pinning the reference is a must.

The sizeof Keyword
The final unsafe-centric C# keyword to consider is sizeof. As in C++, the C# sizeof keyword is used to obtain the size in bytes of an intrinsic data type, but not a custom type, unless within an unsafe context. For example, the following method does not need to be declared “unsafe” as all arguments to the sizeof keyword are intrinsic types:

static void UseSizeOfOperator()
{
Console.WriteLine(“The size of short is {0}.”, sizeof(short)); Console.WriteLine(“The size of int is {0}.”, sizeof(int)); Console.WriteLine(“The size of long is {0}.”, sizeof(long));
}

However, if you want to get the size of your custom Point structure, you need to update this method as so (note the unsafe keyword has been added):

unsafe static void UseSizeOfOperator()
{

unsafe {
Console.WriteLine(“The size of Point is {0}.”, sizeof(Point));
}
}

That wraps up the look at some of the more advanced features of the C# programming language. To make sure we are all on the same page here, I again must say that most of your .NET projects might never need to directly use these features (especially pointers). Nevertheless, as you will see in later chapters, some topics are quite useful, if not required, when working with the LINQ APIs, most notably extension methods and anonymous types.

Summary
The purpose of this chapter was to deepen your understanding of the C# programming language. First, you investigated various advanced type construction techniques (indexer methods, overloaded operators, and custom conversion routines).
Next, you examined the role of extension methods and anonymous types. As you’ll see in some detail in Chapter 13, these features are useful when working with LINQ-centric APIs (though you can use them anywhere in your code, should they be useful). Recall that anonymous methods allow you to quickly model the “shape” of a type, while extension methods allow you to tack on new functionality to types, without the need to subclass.
You spent the remainder of this chapter examining a small set of lesser-known keywords (sizeof, unsafe, etc.) and during the process learned how to work with raw pointer types. As stated throughout the examination of pointer types, most of your C# applications will never need to use them.

发表评论