{"id":304,"date":"2023-10-20T13:21:58","date_gmt":"2023-10-20T05:21:58","guid":{"rendered":"https:\/\/miie.net\/?p=304"},"modified":"2023-10-20T13:21:58","modified_gmt":"2023-10-20T05:21:58","slug":"pro-c10-chapter-8-working-with-interfaces","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=304","title":{"rendered":"Pro C#10 CHAPTER 8 Working with Interfaces"},"content":{"rendered":"<p>PART IV<\/p>\n<p>Advanced C# Programming<\/p>\n<p>CHAPTER 8<\/p>\n<p>Working with Interfaces<\/p>\n<p>This chapter builds upon your current understanding of object-oriented development by examining the topic of interface-based programming. Here, you will learn how to define and implement interfaces and come to understand the benefits of building types that support multiple behaviors. Along the way, you will look at several related topics, such as obtaining interface references, implementing explicit interfaces, and constructing interface hierarchies. You will also examine several standard interfaces defined within the<br \/>\n.NET Core base class libraries. Also covered are the new features introduced in C# 8 regarding interfaces, including default interface methods, static members, and access modifiers. As you will see, your custom classes and structures are free to implement these predefined interfaces to support several useful behaviors, such as object cloning, object enumeration, and object sorting.<\/p>\n<p>Understanding Interface Types<br \/>\nTo begin this chapter, allow me to provide a formal definition of the interface type, which has changed with the introduction of C# 8.0. Prior to C# 8.0, an interface is nothing more than a named set of abstract members. Recall from Chapter 6 that abstract methods are pure protocol, in that they do not provide a default implementation. The specific members defined by an interface depend on the exact behavior it is<br \/>\nmodeling. Said another way, an interface expresses a behavior that a given class or structure may choose to support. Furthermore, as you will see in this chapter, a class or structure can support as many interfaces as necessary, thereby supporting (in essence) multiple behaviors.<br \/>\nThe default interface methods feature, introduced in C# 8.0, allows for interface methods to contain an implementation that may or may not be overridden by the implementing class. More on this later in this chapter.<br \/>\nAs you might guess, the .NET Core base class libraries ship with numerous predefined interface types that are implemented by various classes and structures. For example, as you will see in Chapter 20, ADO.NET ships with multiple data providers that allow you to communicate with a particular database management system. Thus, under ADO.NET, you have numerous connection objects to choose from (SqlConnection, OleDbConnection, OdbcConnection, etc.). In addition, third-party database vendors (as<br \/>\nwell as numerous open source projects) provide .NET libraries to communicate with a wide number of other databases (MySQL, Oracle, etc.), all of which contain objects implementing these interfaces.<br \/>\nAlthough each connection class has a unique name, is defined within a different namespace, and (in some cases) is bundled within a different assembly, all connection classes implement a common interface named IDbConnection.<\/p>\n<p>\/\/ The IDbConnection interface defines a common<br \/>\n\/\/ set of members supported by all connection objects. public interface IDbConnection : IDisposable<br \/>\n{<\/p>\n<p>\u00a9 Andrew Troelsen, Phil Japikse 2022<br \/>\nA. Troelsen and P. Japikse, Pro C# 10 with .NET 6, <a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_8\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_8\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_8\">https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_8<\/a><\/a><\/a><\/p>\n<p>315<\/p>\n<p>\/\/ Methods<br \/>\nIDbTransaction BeginTransaction();<br \/>\nIDbTransaction BeginTransaction(IsolationLevel il); void ChangeDatabase(string databaseName);<br \/>\nvoid Close();<br \/>\nIDbCommand CreateCommand(); void Open();<br \/>\n\/\/ Properties<br \/>\nstring ConnectionString { get; set;} int ConnectionTimeout { get; } string Database { get; } ConnectionState State { get; }<br \/>\n}<\/p>\n<p>\u25a0Note  By convention, .NET interface names are prefixed with a capital letter I. When you are creating your own custom interfaces, it is considered a best practice to do the same.<\/p>\n<p>Do not concern yourself with the details of what these members do at this point. Simply understand that the IDbConnection interface defines a set of members that are common to all ADO.NET connection classes. Given this, you are guaranteed that every connection object supports members such as Open(), Close(), CreateCommand(), and so forth. Furthermore, given that interface members are always abstract, each connection object is free to implement these methods in its own unique manner.<br \/>\nAs you work through the remainder of this book, you will be exposed to dozens of interfaces that ship with the .NET Core base class libraries. As you will see, these interfaces can be implemented on your own custom classes and structures to define types that integrate tightly within the framework. As well, once you understand the usefulness of the interface type, you will certainly find reasons to build your own.<\/p>\n<p>Interface Types vs. Abstract Base Classes<br \/>\nGiven your work in Chapter 6, the interface type might seem somewhat like an abstract base class. Recall that when a class is marked as abstract, it may define any number of abstract members to provide a polymorphic interface to all derived types. However, even when a class does define a set of abstract members, it is also free to define any number of constructors, field data, nonabstract members (with<br \/>\nimplementation), and so on. Interfaces (prior to C# 8.0) contain only member definitions. Now, with C# 8, interfaces can contain member definitions (like abstract members), members with default implementations (like virtual methods), and static members. There are only two real differences: interfaces cannot have nonstatic constructors, and a class can implement multiple interfaces. More on this second point next.<br \/>\nThe polymorphic interface established by an abstract parent class suffers from one major limitation, in that only derived types support the members defined by the abstract parent. However, in larger software systems, it is common to develop multiple class hierarchies that have no common parent beyond System.<br \/>\nObject. Given that abstract members in an abstract base class apply only to derived types, you have no way<br \/>\nto configure types in different hierarchies to support the same polymorphic interface. To begin, create a new Console Application project named CustomInterfaces. Add the following abstract class to the project:<\/p>\n<p>namespace CustomInterfaces;<br \/>\npublic abstract class CloneableType<br \/>\n{<br \/>\n\/\/ Only derived types can support this<br \/>\n\/\/ &quot;polymorphic interface.&quot; Classes in other<\/p>\n<p>\/\/ hierarchies have no access to this abstract<br \/>\n\/\/ member.<br \/>\npublic abstract object Clone();<br \/>\n}<\/p>\n<p>Given this definition, only members that extend CloneableType can support the Clone() method. If you create a new set of classes that do not extend this base class, you cannot gain this polymorphic interface. Also, recall that C# does not support multiple inheritance for classes. Therefore, if you wanted to create a MiniVan that \u201cis-a\u201d Car and \u201cis-a\u201d CloneableType, you are unable to do so.<\/p>\n<p>\/\/ Nope! Multiple inheritance is not possible in C#<br \/>\n\/\/ for classes.<br \/>\npublic class MiniVan : Car, CloneableType<br \/>\n{<br \/>\n}<\/p>\n<p>As you might guess, interface types come to the rescue. After an interface has been defined, it can be implemented by any class or structure, in any hierarchy, and within any namespace or any assembly (written in any .NET Core programming language). As you can see, interfaces are highly polymorphic. Consider the standard .NET Core interface named ICloneable, defined in the System namespace. This interface defines a single method named Clone().<\/p>\n<p>public interface ICloneable<br \/>\n{<br \/>\nobject Clone();<br \/>\n}<\/p>\n<p>If you examine the .NET Core base class libraries, you will find that many seemingly unrelated types (System.Array, System.Data.SqlClient.SqlConnection, System.OperatingSystem, System.String, etc.) all implement this interface. Although these types have no common parent (other than System.Object), you can treat them polymorphically via the ICloneable interface type.<br \/>\nTo get started, clear out the Program.cs code and add the following:<\/p>\n<p>using CustomInterfaces;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> A First Look at Interfaces <\/em><\/strong><\/strong>\\n&quot;); CloneableExample();<\/p>\n<p>Next, add the following local function named CloneMe() to your top-level statements. This function takes an ICloneable interface parameter, which accepts any object that implements this interface. Here is the function code:<\/p>\n<p>static void CloneableExample()<br \/>\n{<br \/>\n\/\/ All of these classes support the ICloneable interface. string myStr = &quot;Hello&quot;;<br \/>\nOperatingSystem unixOS = new OperatingSystem(PlatformID.Unix, new Version());<br \/>\n\/\/ Therefore, they can all be passed into a method taking ICloneable. CloneMe(myStr);<\/p>\n<p>CloneMe(unixOS);<br \/>\nstatic void CloneMe(ICloneable c)<br \/>\n{<br \/>\n\/\/ Clone whatever we get and print out the name. object theClone = c.Clone();<br \/>\nConsole.WriteLine(&quot;Your clone is a: {0}&quot;, theClone.GetType().Name);<br \/>\n}<br \/>\n}<\/p>\n<p>When you run this application, the class name of each class prints to the console via the GetType() method you inherit from System.Object. As will be explained in detail in Chapter 17, this method allows you to understand the composition of any type at runtime. In any case, the output of the previous program is shown next:<\/p>\n<p><strong><strong><em> A First Look at Interfaces <\/em><\/strong><\/strong> Your clone is a: String<br \/>\nYour clone is a: OperatingSystem<\/p>\n<p>Another limitation of abstract base classes is that each derived type must contend with the set of abstract members and provide an implementation. To see this problem, recall the shapes hierarchy you defined in Chapter 6. Assume you defined a new abstract method in the Shape base class named GetNumberOfPoints(), which allows derived types to return the number of points required to render the shape.<\/p>\n<p>namespace CustomInterfaces; abstract class Shape<br \/>\n{<br \/>\n...<br \/>\n\/\/ Every derived class must now support this method! public abstract byte GetNumberOfPoints();<br \/>\n}<\/p>\n<p>Clearly, the only class that has any points in the first place is Hexagon. However, with this update, every derived class (Circle, Hexagon, and ThreeDCircle) must now provide a concrete implementation of this function, even if it makes no sense to do so. Again, the interface type provides a solution. If you define an interface that represents the behavior of \u201chaving points,\u201d you can simply plug it into the Hexagon type, leaving Circle and ThreeDCircle untouched.<\/p>\n<p>\u25a0Note The changes to interfaces in C# 8 are probably the most significant changes to an existing language feature that i can recall. as described earlier, the new interface capabilities move them significantly closer to the functionality of abstract classes, with the added ability for a class to implement multiple interfaces. My advice is to tread carefully in these waters and use common sense. Just because you can do something does not mean that you should.<\/p>\n<p>Defining Custom Interfaces<br \/>\nNow that you better understand the overall role of interface types, let\u2019s see an example of defining and implementing custom interfaces. Copy the Shape.cs, Hexagon.cs, Circle.cs, and ThreeDCircle.cs files from the Shapes solution you created in Chapter 6. After you have done so, rename the namespace that defines your shape-centric types to CustomInterfaces (simply to avoid having to import namespace definitions in your new project). Now, insert a new file into your project named IPointy.cs.<br \/>\nAt a syntactic level, an interface is defined using the C# interface keyword. Unlike a class, interfaces never specify a base class (not even System.Object; however, as you will see later in this chapter, an interface can specify base interfaces). Prior to C# 8.0, the members of an interface never specify an access modifier (as all interface members are implicitly public and abstract). New in C# 8.0, private, internal, protected, and even static members can also be defined. More on this later. To get the ball rolling, here is a custom interface defined in C#:<\/p>\n<p>namespace CustomInterfaces;<br \/>\n\/\/ This interface defines the behavior of &quot;having points.&quot; public interface IPointy<br \/>\n{<br \/>\n\/\/ Implicitly public and abstract. byte GetNumberOfPoints();<br \/>\n}<\/p>\n<p>Interfaces in C# 8 cannot define data fields or nonstatic constructors. Hence, the following version of<br \/>\nIPointy will result in various compiler errors:<\/p>\n<p>\/\/ Ack! Errors abound!<br \/>\npublic interface IPointy<br \/>\n{<br \/>\n\/\/ Error! Interfaces cannot have data fields! public int numbOfPoints;<br \/>\n\/\/ Error! Interfaces do not have nonstatic constructors! public IPointy() { numbOfPoints = 0;}<br \/>\n}<\/p>\n<p>In any case, this initial IPointy interface defines a single method. Interface types are also able to define any number of property prototypes. For example, we could update the IPointy interface to use a read-write property (commented out) and a read-only property. The Points property replaces the GetNumberOfPoints() method.<\/p>\n<p>\/\/ The pointy behavior as a read-only property. public interface IPointy<br \/>\n{<br \/>\n\/\/ Implicitly public and abstract.<br \/>\n\/\/byte GetNumberOfPoints();<\/p>\n<p>\/\/ A read-write property in an interface would look like:<br \/>\n\/\/string PropName { get; set; }<\/p>\n<p>\/\/ while a read-only property in an interface would be: byte Points { get; }<br \/>\n}<\/p>\n<p>\u25a0Note  interface types can also contain event (see Chapter 12) and indexer (see Chapter 11) definitions.<\/p>\n<p>Interface types are quite useless on their own, as you cannot allocate interface types as you would a class or structure.<\/p>\n<p>\/\/ Ack! Illegal to allocate interface types. IPointy p = new IPointy(); \/\/ Compiler error!<\/p>\n<p>Interfaces do not bring much to the table until they are implemented by a class or structure. Here, IPointy is an interface that expresses the behavior of \u201chaving points.\u201d The idea is simple: some classes in the shapes hierarchy have points (such as the Hexagon), while others (such as the Circle) do not.<\/p>\n<p>Implementing an Interface<br \/>\nWhen a class (or structure) chooses to extend its functionality by supporting interfaces, it does so using a comma-delimited list in the type definition. Be aware that the direct base class must be the first item listed after the colon operator. When your class type derives directly from System.Object, you are free to simply list the interface (or interfaces) supported by the class, as the C# compiler will extend your types<br \/>\nfrom System.Object if you do not say otherwise. On a related note, given that structures always derive from System.ValueType (see Chapter 4), simply list each interface directly after the structure definition. Ponder the following examples:<\/p>\n<p>\/\/ This class derives from System.Object and<br \/>\n\/\/ implements a single interface. public class Pencil : IPointy<br \/>\n{...}<\/p>\n<p>\/\/ This class also derives from System.Object<br \/>\n\/\/ and implements a single interface. public class SwitchBlade : object, IPointy<br \/>\n{...}<\/p>\n<p>\/\/ This class derives from a custom base class<br \/>\n\/\/ and implements a single interface. public class Fork : Utensil, IPointy<br \/>\n{...}<\/p>\n<p>\/\/ This struct implicitly derives from System.ValueType and<br \/>\n\/\/ implements two interfaces.<br \/>\npublic struct PitchFork : ICloneable, IPointy<br \/>\n{...}<\/p>\n<p>Understand that implementing an interface is an all-or-nothing proposition for interface items that do not include a default implementation. The supporting type is not able to selectively choose which members it will implement. Given that the IPointy interface defines a single read-only property, this is not too much of a burden. However, if you are implementing an interface that defines ten members (such as<br \/>\nthe IDbConnection interface shown earlier), the type is now responsible for fleshing out the details of all ten abstract members.<\/p>\n<p>For this example, insert a new class type named Triangle that \u201cis-a\u201d Shape and supports IPointy.<br \/>\nNote that the implementation of the read-only Points property (implemented using the expression-bodied member syntax) simply returns the correct number of points (three).<\/p>\n<p>namespace CustomInterfaces;<br \/>\n\/\/ New Shape derived class named Triangle. class Triangle : Shape, IPointy<br \/>\n{<br \/>\npublic Triangle() { }<br \/>\npublic Triangle(string name) : base(name) { } public override void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing {0} the Triangle&quot;, PetName);<br \/>\n}<\/p>\n<p>\/\/ IPointy implementation.<br \/>\n\/\/public byte Points<br \/>\n\/\/{<br \/>\n\/\/  get { return 3; }<br \/>\n\/\/}<br \/>\npublic byte Points =&gt; 3;<br \/>\n}<\/p>\n<p>Now, update your existing Hexagon type to also support the IPointy interface type.<\/p>\n<p>namespace CustomInterfaces;<br \/>\n\/\/ Hexagon now implements IPointy. class Hexagon : Shape, IPointy<br \/>\n{<br \/>\npublic Hexagon(){ }<br \/>\npublic Hexagon(string name) : base(name){ } public override void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing {0} the Hexagon&quot;, PetName);<br \/>\n}<\/p>\n<p>\/\/ IPointy implementation.<br \/>\npublic byte Points =&gt; 6;<br \/>\n}<\/p>\n<p>To sum up the story so far, the Visual Studio class diagram shown in Figure 8-1 illustrates IPointy- compatible classes using the popular \u201clollipop\u201d notation. Notice again that Circle and ThreeDCircle do not implement IPointy, as this behavior makes no sense for these classes.<\/p>\n<p>Figure 8-1. The shapes hierarchy, now with interfaces<\/p>\n<p>\u25a0Note To display or hide interface names in the class designer, right-click the interface icon and select the Collapse or Expand option.<\/p>\n<p>Invoking Interface Members at the Object Level<br \/>\nNow that you have some classes that support the IPointy interface, the next question is how you interact with the new functionality. The most straightforward way to interact with functionality supplied by a given interface is to invoke the members directly from the object level (provided the interface members are not implemented explicitly; you can find more details later in the section \u201cExplicit Interface Implementation\u201d). For example, consider the following code:<\/p>\n<p>using CustomInterfaces;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Interfaces <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n\/\/ Call Points property defined by IPointy. Hexagon hex = new Hexagon(); Console.WriteLine(&quot;Points: {0}&quot;, hex.Points); Console.ReadLine();<\/p>\n<p>This approach works fine in this case, given that you understand the Hexagon type has implemented the interface in question and, therefore, has a Points property. Other times, however, you might not be able to determine which interfaces are supported by a given type. For example, suppose you have an array containing 50 Shape-compatible types, only some of which support IPointy. Obviously, if you attempt to invoke the Points property on a type that has not implemented IPointy, you will receive an error. So, how can you dynamically determine whether a class or structure supports the correct interface?<br \/>\nOne way to determine at runtime whether a type supports a specific interface is to use an explicit cast. If the type does not support the requested interface, you receive an InvalidCastException. To handle this possibility gracefully, use structured exception handling as in the following example:<\/p>\n<p>...<br \/>\n\/\/ Catch a possible InvalidCastException. Circle c = new Circle(&quot;Lisa&quot;);<br \/>\nIPointy itfPt = null; try<br \/>\n{<br \/>\nitfPt = (IPointy)c; Console.WriteLine(itfPt.Points);<br \/>\n}<br \/>\ncatch (InvalidCastException e)<br \/>\n{<br \/>\nConsole.WriteLine(e.Message);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>While you could use try\/catch logic and hope for the best, it would be ideal to determine which interfaces are supported before invoking the interface members in the first place. Let\u2019s see two ways of doing so.<\/p>\n<p>Obtaining Interface References: The as Keyword<br \/>\nYou can determine whether a given type supports an interface by using the as keyword, introduced in Chapter 6. If the object can be treated as the specified interface, you are returned a reference to the interface in question. If not, you receive a null reference. Therefore, be sure to check against a null value before proceeding.<\/p>\n<p>...<br \/>\n\/\/ Can we treat hex2 as IPointy? Hexagon hex2 = new Hexagon(&quot;Peter&quot;); IPointy itfPt2 = hex2 as IPointy; if(itfPt2 != null)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Points: {0}&quot;, itfPt2.Points);<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nConsole.WriteLine(&quot;OOPS! Not pointy...&quot;);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Notice that when you use the as keyword, you have no need to use try\/catch logic; if the reference is not null, you know you are calling on a valid interface reference.<\/p>\n<p>Obtaining Interface References: The is Keyword (Updated 7.0)<br \/>\nYou may also check for an implemented interface using the is keyword (also first discussed in Chapter 6). If the object in question is not compatible with the specified interface, you are returned the value false. If you supply a variable name in the statement, the type is assigned into the variable, eliminating the need to do the type check and perform a cast. The previous example is updated here:<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Interfaces <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\nif(hex2 is IPointy itfPt3)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Points: {0}&quot;, itfPt3.Points);<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nConsole.WriteLine(&quot;OOPS! Not pointy...&quot;);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Default Implementations (New 8.0)<br \/>\nAs mentioned earlier, C# 8.0 added the ability for interface methods and properties to have a default implementation. Add a new interface named IRegularPointy to represent a regularly shaped polygon. The code is shown here:<\/p>\n<p>namespace CustomInterfaces; interface IRegularPointy : IPointy<br \/>\n{<br \/>\nint SideLength { get; set; } int NumberOfSides { get; set; }<br \/>\nint Perimeter =&gt; SideLength * NumberOfSides;<br \/>\n}<\/p>\n<p>Add a new class named Square.cs to the project, inherit the Shape base class, and implement the<br \/>\nIRegularPointy interface, as follows:<\/p>\n<p>namespace CustomInterfaces;<br \/>\nclass Square: Shape,IRegularPointy<br \/>\n{<br \/>\npublic Square() { }<br \/>\npublic Square(string name) : base(name) { }<br \/>\n\/\/Draw comes from the Shape base class public override void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing a square&quot;);<br \/>\n}<\/p>\n<p>\/\/This comes from the IPointy interface public byte Points =&gt; 4;<\/p>\n<p>\/\/These come from the IRegularPointy interface public int SideLength { get; set; }<br \/>\npublic int NumberOfSides { get; set; }<br \/>\n\/\/Note that the Perimeter property is not implemented<br \/>\n}<\/p>\n<p>Here we have unwittingly introduced the first \u201ccatch\u201d of using default implementations in interfaces.<br \/>\nThe Perimeter property, defined on the IRegularPointy interface, is not defined in the Square class, making it inaccessible from an instance of Square. To see this in action, create a new instance of a Square class and output the relevant values to the console, like this:<\/p>\n<p>Console.WriteLine(&quot;\\n<strong><strong><em> Fun with Interfaces <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\nvar sq = new Square(&quot;Boxy&quot;)<br \/>\n{NumberOfSides = 4, SideLength = 4}; sq.Draw();<br \/>\n\/\/This won't compile<br \/>\n\/\/Console.WriteLine($&quot;{sq.PetName} has {sq.NumberOfSides} of length {sq.SideLength} and a perimeter of {sq.Perimeter}&quot;);<\/p>\n<p>Instead, the Square instance must be explicitly cast to the IRegularPointy interface (since that is where the implementation lives), and then the Perimeter property can be accessed. Update the code to the following:<\/p>\n<p>Console.WriteLine($&quot;{sq.PetName} has {sq.NumberOfSides} of length {sq.SideLength} and a perimeter of {((IRegularPointy)sq).Perimeter}&quot;);<\/p>\n<p>One option to get around this problem is to always code to the interface of a type. Change the definition of the Square instance to IRegularPointy instead of Square, like this:<\/p>\n<p>IRegularPointy sq = new Square(&quot;Boxy&quot;) {NumberOfSides = 4, SideLength = 4};<\/p>\n<p>The problem with this approach (in our case) is that the Draw() method and PetName property are not defined on the interface, causing compilation errors.<br \/>\nWhile this is a trivial example, it does demonstrate one of the problems with default interfaces. Before using this feature in your code, make sure you measure the implications of the calling code having to know where the implementation exists.<\/p>\n<p>Static Constructors and Members (New 8.0)<br \/>\nAnother addition to interfaces in C# 8.0 is the ability to have static constructors and members, which function the same as static members on class definitions but are defined on interfaces. Update the IRegularPointy interface with an example static property and a static constructor.<\/p>\n<p>interface IRegularPointy : IPointy<br \/>\n{<br \/>\nint SideLength { get; set; } int NumberOfSides { get; set; }<br \/>\nint Perimeter =&gt; SideLength * NumberOfSides;<\/p>\n<p>\/\/Static members are also allowed in C# 8 static string ExampleProperty { get; set; }<\/p>\n<p>static IRegularPointy() =&gt; ExampleProperty = &quot;Foo&quot;;<br \/>\n}<\/p>\n<p>Static constructors must be parameterless and can only access static properties and methods. To access the interface static property, add the following code to the top-level statements:<\/p>\n<p>Console.WriteLine($&quot;Example property: {IRegularPointy.ExampleProperty}&quot;); IRegularPointy.ExampleProperty = &quot;Updated&quot;;<br \/>\nConsole.WriteLine($&quot;Example property: {IRegularPointy.ExampleProperty}&quot;);<\/p>\n<p>Notice how the static property must be invoked from the interface and not an instance variable.<\/p>\n<p>Interfaces as Parameters<br \/>\nGiven that interfaces are valid types, you may construct methods that take interfaces as parameters, as illustrated by the CloneMe() method earlier in this chapter. For the current example, assume you have defined another interface named IDraw3D.<\/p>\n<p>namespace CustomInterfaces;<br \/>\n\/\/ Models the ability to render a type in stunning 3D. public interface IDraw3D<br \/>\n{<br \/>\nvoid Draw3D();<br \/>\n}<\/p>\n<p>Next, assume that two of your three shapes (ThreeDCircle and Hexagon) have been configured to support this new behavior.<\/p>\n<p>\/\/ Circle supports IDraw3D.<br \/>\nclass ThreeDCircle : Circle, IDraw3D<br \/>\n{<br \/>\n...<br \/>\npublic void Draw3D()<br \/>\n=&gt; Console.WriteLine(&quot;Drawing Circle in 3D!&quot;); }<br \/>\n}<\/p>\n<p>\/\/ Hexagon supports IPointy and IDraw3D. class Hexagon : Shape, IPointy, IDraw3D<br \/>\n{<br \/>\n...<br \/>\npublic void Draw3D()<br \/>\n=&gt; Console.WriteLine(&quot;Drawing Hexagon in 3D!&quot;);<br \/>\n}<\/p>\n<p>Figure 8-2 presents the updated Visual Studio class diagram.<\/p>\n<p>Figure 8-2. The updated shapes hierarchy<\/p>\n<p>If you now define a method taking an IDraw3D interface as a parameter, you can effectively send in any object implementing IDraw3D. If you attempt to pass in a type not supporting the necessary interface, you receive a compile-time error. Consider the following method defined within your Program.cs file:<\/p>\n<p>\/\/ I'll draw anyone supporting IDraw3D. static void DrawIn3D(IDraw3D itf3d)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;-&gt; Drawing IDraw3D compatible type&quot;); itf3d.Draw3D();<br \/>\n}<\/p>\n<p>You could now test whether an item in the Shape array supports this new interface and, if so, pass it into the DrawIn3D() method for processing.<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Interfaces <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\nShape[] myShapes = { new Hexagon(), new Circle(), new Triangle(&quot;Joe&quot;), new Circle(&quot;JoJo&quot;) } ;<br \/>\nfor(int i = 0; i &lt; myShapes.Length; i++)<br \/>\n{<br \/>\n\/\/ Can I draw you in 3D?<br \/>\nif (myShapes[i] is IDraw3D s)<br \/>\n{<br \/>\nDrawIn3D(s);<br \/>\n}<br \/>\n}<\/p>\n<p>Here is the output of the updated application. Notice that only the Hexagon object prints out in 3D, as the other members of the Shape array do not implement the IDraw3D interface.<\/p>\n<p><strong><strong><em> Fun with Interfaces <\/em><\/strong><\/strong><br \/>\n...<br \/>\n-&gt; Drawing IDraw3D compatible type Drawing Hexagon in 3D!<\/p>\n<p>Interfaces as Return Values<br \/>\nInterfaces can also be used as method return values. For example, you could write a method that takes an array of Shape objects and returns a reference to the first item that supports IPointy.<\/p>\n<p>\/\/ This method returns the first object in the<br \/>\n\/\/ array that implements IPointy.<br \/>\nstatic IPointy FindFirstPointyShape(Shape[] shapes)<br \/>\n{<br \/>\nforeach (Shape s in shapes)<br \/>\n{<br \/>\nif (s is IPointy ip)<br \/>\n{<br \/>\nreturn ip;<br \/>\n}<br \/>\n}<br \/>\nreturn null;<br \/>\n}<\/p>\n<p>You could interact with this method as follows:<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Interfaces <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\n\/\/ Make an array of Shapes.<br \/>\nShape[] myShapes = { new Hexagon(), new Circle(),<br \/>\nnew Triangle(&quot;Joe&quot;), new Circle(&quot;JoJo&quot;)};<\/p>\n<p>\/\/ Get first pointy item.<br \/>\nIPointy firstPointyItem = FindFirstPointyShape(myShapes);<br \/>\n\/\/ To be safe, use the null conditional operator. Console.WriteLine(&quot;The item has {0} points&quot;, firstPointyItem?.Points);<\/p>\n<p>Arrays of Interface Types<br \/>\nRecall that the same interface can be implemented by numerous types, even if they are not within the same class hierarchy and do not have a common parent class beyond System.Object. This can yield some powerful programming constructs. For example, assume you have developed three new class types<\/p>\n<p>within your current project that model kitchen utensils (via Knife and Fork classes) and another modeling gardening equipment (\u00e0 la PitchFork). The relevant code for the classes is shown here, and the updated class diagram is shown in Figure 8-3:<\/p>\n<p>\/\/Fork.cs<br \/>\nnamespace CustomInterfaces; class Fork : IPointy<br \/>\n{<br \/>\npublic byte Points =&gt; 4;<br \/>\n}<br \/>\n\/\/PitchFork.cs<br \/>\nnamespace CustomInterfaces; class PitchFork : IPointy<br \/>\n{<br \/>\npublic byte Points =&gt; 3;<br \/>\n}<br \/>\n\/\/Knife.cs.cs<br \/>\nnamespace CustomInterfaces; class Knife : IPointy<br \/>\n{<br \/>\npublic byte Points =&gt; 1;<br \/>\n}<\/p>\n<p>Figure 8-3. Recall that interfaces can be \u201cplugged into\u201d any type in any part of a class hierarchy.<\/p>\n<p>If you defined the PitchFork, Fork, and Knife types, you could now define an array of IPointy- compatible objects. Given that these members all support the same interface, you can iterate through the array and treat each item as an IPointy-compatible object, regardless of the overall diversity of the class hierarchies.<\/p>\n<p>...<br \/>\n\/\/ This array can only contain types that<br \/>\n\/\/ implement the IPointy interface.<br \/>\nIPointy[] myPointyObjects = {new Hexagon(), new Knife(), new Triangle(), new Fork(), new PitchFork()};<\/p>\n<p>foreach(IPointy i in myPointyObjects)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Object has {0} points.&quot;, i.Points);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Just to highlight the importance of this example, remember this: when you have an array of a given interface, the array can contain any class or structure that implements that interface.<\/p>\n<p>Implementing Interfaces Using Visual Studio or Visual Studio Code<br \/>\nAlthough interface-based programming is a powerful technique, implementing interfaces may entail a healthy amount of typing. Given that interfaces are a named set of abstract members, you are required to type in the definition and implementation for each interface method on each type that supports the behavior. Therefore, if you want to support an interface that defines a total of five methods and three properties, you need to account for all eight members (or else you will receive compiler errors).<br \/>\nAs you would hope, Visual Studio and Visual Studio Code both support various tools that make the task of implementing interfaces less burdensome. By way of a simple test, insert a final class into your current project named PointyTestClass. When you add an interface such as IPointy (or any interface for that matter) to a class type, you might have noticed that when you complete typing the interface\u2019s name (or when you position the mouse cursor on the interface name in the code window), Visual Studio and Visual Studio Code both add a lightbulb, which can also be invoked with the Ctrl+period (.) key combination. When you click the lightbulb, you will be presented with a drop-down list that allows you to implement the interface (see Figures 8-4 and 8-5).<\/p>\n<p>Figure 8-4. Implementing interfaces automatically using Visual Studio Code<\/p>\n<p>Figure 8-5. Implementing interfaces automatically using Visual Studio<\/p>\n<p>Notice you are presented with two options, the second of which (explicit interface implementation) will be examined in the next section. For the time being, select the first option, and you will see that Visual<br \/>\nStudio\/Visual Studio Code has generated stub code for you to update. (Note that the default implementation throws a System.NotImplementedException, which can obviously be deleted.)<\/p>\n<p>namespace CustomInterfaces; class PointyTestClass : IPointy<br \/>\n{<br \/>\npublic byte Points =&gt; throw new NotImplementedException();<br \/>\n}<\/p>\n<p>\u25a0Note Visual studio \/Visual studio Code also supports extract interface refactoring, available from the Extract interface option of the Quick actions menu. This allows you to pull out a new interface definition from an existing class definition. for example, you might be halfway through writing a class when it dawns on you that you can generalize the behavior into an interface (and thereby open the possibility of alternative implementations).<\/p>\n<p>Explicit Interface Implementation<br \/>\nAs shown earlier in this chapter, a class or structure can implement any number of interfaces. Given this, there is always the possibility you might implement interfaces that contain identical members and,<br \/>\ntherefore, have a name clash to contend with. To illustrate various manners in which you can resolve this issue, create a new Console Application project named InterfaceNameClash. Now design three interfaces that represent various locations to which an implementing type could render its output.<\/p>\n<p>namespace InterfaceNameClash;<br \/>\n\/\/ Draw image to a form. public interface IDrawToForm<br \/>\n{<br \/>\nvoid Draw();<br \/>\n}<\/p>\n<p>namespace InterfaceNameClash;<br \/>\n\/\/ Draw to buffer in memory. public interface IDrawToMemory<\/p>\n<p>{<br \/>\nvoid Draw();<br \/>\n}<\/p>\n<p>namespace InterfaceNameClash;<br \/>\n\/\/ Render to the printer. public interface IDrawToPrinter<br \/>\n{<br \/>\nvoid Draw();<br \/>\n}<\/p>\n<p>Notice that each interface defines a method named Draw(), with the identical signature. If you now want to support each of these interfaces on a single class type named Octagon, the compiler will allow the following definition:<\/p>\n<p>namespace InterfaceNameClash;<br \/>\nclass Octagon : IDrawToForm, IDrawToMemory, IDrawToPrinter<br \/>\n{<br \/>\npublic void Draw()<br \/>\n{<br \/>\n\/\/ Shared drawing logic. Console.WriteLine(&quot;Drawing the Octagon...&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Although the code compiles cleanly, you do have a possible problem. Simply put, providing a single implementation of the Draw() method does not allow you to take unique courses of action based on which interface is obtained from an Octagon object. For example, the following code will invoke the same Draw() method, regardless of which interface you obtain:<\/p>\n<p>using InterfaceNameClash;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Interface Name Clashes <\/em><\/strong><\/strong>\\n&quot;); Octagon oct = new Octagon();<\/p>\n<p>\/\/ Both of these invocations call the<br \/>\n\/\/ same Draw() method!<\/p>\n<p>\/\/ Shorthand notation if you don't need<br \/>\n\/\/ the interface variable for later use.<br \/>\n((IDrawToPrinter)oct).Draw();<\/p>\n<p>\/\/ Could also use the &quot;is&quot; keyword.<br \/>\nif (oct is IDrawToMemory dtm)<br \/>\n{<br \/>\ndtm.Draw();<br \/>\n}<\/p>\n<p>Console.ReadLine();<\/p>\n<p>Clearly, the sort of code required to render the image to a window is quite different from the code needed to render the image to a networked printer or a region of memory. When you implement several interfaces that have identical members, you can resolve this sort of name clash using explicit interface implementation syntax. Consider the following update to the Octagon type:<\/p>\n<p>class Octagon : IDrawToForm, IDrawToMemory, IDrawToPrinter<br \/>\n{<br \/>\n\/\/ Explicitly bind Draw() implementations<br \/>\n\/\/ to a given interface.<br \/>\nvoid IDrawToForm.Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing to form...&quot;);<br \/>\n}<br \/>\nvoid IDrawToMemory.Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing to memory...&quot;);<br \/>\n}<br \/>\nvoid IDrawToPrinter.Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing to a printer...&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>As you can see, when explicitly implementing an interface member, the general pattern breaks down to this:<\/p>\n<p>returnType InterfaceName.MethodName(params){}<\/p>\n<p>Note that when using this syntax, you do not supply an access modifier; explicitly implemented members are automatically private. For example, the following is illegal syntax:<\/p>\n<p>\/\/ Error! No access modifier! public void IDrawToForm.Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing to form...&quot;);<br \/>\n}<\/p>\n<p>Because explicitly implemented members are always implicitly private, these members are no longer available from the object level. In fact, if you were to apply the dot operator to an Octagon type, you would find that IntelliSense does not show you any of the Draw() members. As expected, you must use explicit casting to access the required functionality. The previous code in the top-level statements is already using explicit casting, so it works with explicit interfaces.<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Interface Name Clashes <\/em><\/strong><\/strong>\\n&quot;); Octagon oct = new Octagon();<\/p>\n<p>\/\/ We now must use casting to access the Draw()<br \/>\n\/\/ members.<br \/>\nIDrawToForm itfForm = (IDrawToForm)oct; itfForm.Draw();<\/p>\n<p>\/\/ Shorthand notation if you don't need<br \/>\n\/\/ the interface variable for later use.<br \/>\n((IDrawToPrinter)oct).Draw();<\/p>\n<p>\/\/ Could also use the &quot;is&quot; keyword.<br \/>\nif (oct is IDrawToMemory dtm)<br \/>\n{<br \/>\ndtm.Draw();<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>While this syntax is quite helpful when you need to resolve name clashes, you can use explicit interface implementation simply to hide more \u201cadvanced\u201d members from the object level. In this way, when the object user applies the dot operator, the user will see only a subset of the type\u2019s overall functionality.<br \/>\nHowever, those who require the more advanced behaviors can extract the desired interface via an explicit cast.<\/p>\n<p>Designing Interface Hierarchies<br \/>\nInterfaces can be arranged in an interface hierarchy. Like a class hierarchy, when an interface extends an existing interface, it inherits the abstract members defined by the parent (or parents). Prior to C# 8, derived interfaces never inherit true implementation. Rather, a derived interface simply extends its own definition with additional abstract members. In C# 8, derived interfaces inherit the default implementations as well as extend the definition and potentially add new default implementations.<br \/>\nInterface hierarchies can be useful when you want to extend the functionality of an existing interface without breaking existing code bases. To illustrate, create a new Console Application project named InterfaceHierarchy. Now, let\u2019s design a new set of rendering-centric interfaces such that IDrawable is the root of the family tree.<\/p>\n<p>namespace InterfaceHierarchy; public interface IDrawable<br \/>\n{<br \/>\nvoid Draw();<br \/>\n}<\/p>\n<p>Given that IDrawable defines a basic drawing behavior, you could now create a derived interface that extends this interface with the ability to render in modified formats. Here is an example:<\/p>\n<p>namespace InterfaceHierarchy;<br \/>\npublic interface IAdvancedDraw : IDrawable<br \/>\n{<br \/>\nvoid DrawInBoundingBox(int top, int left, int bottom, int right); void DrawUpsideDown();<br \/>\n}<\/p>\n<p>Given this design, if a class were to implement IAdvancedDraw, it would now be required to implement every member defined up the chain of inheritance (specifically, the Draw(), DrawInBoundingBox(), and DrawUpsideDown() methods).<\/p>\n<p>namespace InterfaceHierarchy;<br \/>\npublic class BitmapImage : IAdvancedDraw<br \/>\n{<br \/>\npublic void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing...&quot;);<br \/>\n}<br \/>\npublic void DrawInBoundingBox(int top, int left, int bottom, int right)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing in a box...&quot;);<br \/>\n}<br \/>\npublic void DrawUpsideDown()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing upside down!&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Now, when you use the BitmapImage, you can invoke each method at the object level (as they are all<br \/>\npublic), as well as extract a reference to each supported interface explicitly via casting.<\/p>\n<p>using InterfaceHierarchy;<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Simple Interface Hierarchy <\/em><\/strong><\/strong>&quot;);<br \/>\n\/\/ Call from object level.<br \/>\nBitmapImage myBitmap = new BitmapImage(); myBitmap.Draw(); myBitmap.DrawInBoundingBox(10, 10, 100, 150); myBitmap.DrawUpsideDown();<\/p>\n<p>\/\/ Get IAdvancedDraw explicitly.<br \/>\nif (myBitmap is IAdvancedDraw iAdvDraw)<br \/>\n{<br \/>\niAdvDraw.DrawUpsideDown();<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Interface Hierarchies with Default Implementations (New 8.0)<br \/>\nWhen interface hierarchies also include default implementations, downstream interfaces can choose to carry the implementation from the base interface or create a new default implementation. Update the IDrawable interface to the following:<\/p>\n<p>public interface IDrawable<br \/>\n{<br \/>\nvoid Draw();<br \/>\nint TimeToDraw() =&gt; 5;<br \/>\n}<\/p>\n<p>Next, update the top-level statements to the following:<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Simple Interface Hierarchy <\/em><\/strong><\/strong>&quot;);<br \/>\n...<br \/>\nif (myBitmap is IAdvancedDraw iAdvDraw)<br \/>\n{<br \/>\niAdvDraw.DrawUpsideDown();<br \/>\nConsole.WriteLine($&quot;Time to draw: {iAdvDraw.TimeToDraw()}&quot;);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Not only does this code compile, but it outputs a value of 5 for the TimeToDraw() method. This is because default implementations automatically carry forward to descendant interfaces. Casting the BitMapImage to the IAdvancedDraw interface provides access to the TimeToDraw() method, even though the BitMapImage instance does not have access to the default implementation. To prove this, enter the following code and see the compile error:<\/p>\n<p>\/\/This does not compile myBitmap.TimeToDraw();<\/p>\n<p>If a downstream interface wants to provide its own default implementation, it must hide the upstream implementation. For example, if the IAdvancedDraw TimeToDraw() method takes 15 units to draw, update the interface to the following definition:<\/p>\n<p>public interface IAdvancedDraw : IDrawable<br \/>\n{<br \/>\nvoid DrawInBoundingBox(<br \/>\nint top, int left, int bottom, int right); void DrawUpsideDown();<br \/>\nnew int TimeToDraw() =&gt; 15;<br \/>\n}<\/p>\n<p>Of course, the BitMapImage class is also free to implement the TimeToDraw() method. Unlike the<br \/>\nIAdvancedDraw TimeToDraw() method, the class only needs to implement the method, not hide it.<\/p>\n<p>public class BitmapImage : IAdvancedDraw<br \/>\n{<br \/>\n...<br \/>\npublic int TimeToDraw() =&gt; 12;<br \/>\n}<\/p>\n<p>When casting the BitmapImage instance to either the IAdvancedDraw or IDrawable interface, the method on the instance still is executed. Add this code to the top-level statements:<\/p>\n<p>\/\/Always calls method on instance:<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Calling Implemented TimeToDraw <\/em><\/strong><\/strong>&quot;); Console.WriteLine($&quot;Time to draw: {myBitmap.TimeToDraw()}&quot;); Console.WriteLine($&quot;Time to draw: {((IDrawable) myBitmap).TimeToDraw()}&quot;); Console.WriteLine($&quot;Time to draw: {((IAdvancedDraw) myBitmap).TimeToDraw()}&quot;);<\/p>\n<p>Here are the results:<\/p>\n<p><strong><strong><em> Simple Interface Hierarchy <\/em><\/strong><\/strong><br \/>\n...<br \/>\n<strong><strong><em> Calling Implemented TimeToDraw <\/em><\/strong><\/strong> Time to draw: 12<br \/>\nTime to draw: 12 Time to draw: 12<\/p>\n<p>Multiple Inheritance with Interface Types<br \/>\nUnlike class types, an interface can extend multiple base interfaces, allowing you to design some powerful and flexible abstractions. Create a new Console Application project named MiInterfaceHierarchy. Here is another collection of interfaces that model various rendering and shape abstractions. Notice that the IShape interface is extending both IDrawable and IPrintable.<\/p>\n<p>\/\/IDrawable.cs<br \/>\nnamespace MiInterfaceHierarchy;<br \/>\n\/\/ Multiple inheritance for interface types is A-okay. interface IDrawable<br \/>\n{<br \/>\nvoid Draw();<br \/>\n}<\/p>\n<p>\/\/IPrintable.cs<br \/>\nnamespace MiInterfaceHierarchy; interface IPrintable<br \/>\n{<br \/>\nvoid Print();<br \/>\nvoid Draw(); \/\/ &lt;-- Note possible name clash here!<br \/>\n}<\/p>\n<p>\/\/IShape.cs<br \/>\nnamespace MiInterfaceHierarchy;<br \/>\n\/\/ Multiple interface inheritance. OK! interface IShape : IDrawable, IPrintable<br \/>\n{<br \/>\nint GetNumberOfSides();<br \/>\n}<\/p>\n<p>Figure 8-6 illustrates the current interface hierarchy.<\/p>\n<p>Figure 8-6. Unlike classes, interfaces can extend multiple interface types<\/p>\n<p>At this point, the million-dollar question is \u201cIf you have a class supporting IShape, how many methods will it be required to implement?\u201d The answer: it depends. If you want to provide a simple implementation of the Draw() method, you need to provide only three members, as shown in the following Rectangle type:<\/p>\n<p>namespace MiInterfaceHierarchy; class Rectangle : IShape<br \/>\n{<br \/>\npublic int GetNumberOfSides() =&gt; 4;<br \/>\npublic void Draw() =&gt; Console.WriteLine(&quot;Drawing...&quot;); public void Print() =&gt; Console.WriteLine(&quot;Printing...&quot;);<br \/>\n}<\/p>\n<p>If you would rather have specific implementations for each Draw() method (which in this case would make the most sense), you can resolve the name clash using explicit interface implementation, as shown in the following Square type:<\/p>\n<p>namespace MiInterfaceHierarchy; class Square : IShape<br \/>\n{<br \/>\n\/\/ Using explicit implementation to handle member name clash. void IPrintable.Draw()<br \/>\n{<br \/>\n\/\/ Draw to printer ...<br \/>\n}<br \/>\nvoid IDrawable.Draw()<br \/>\n{<br \/>\n\/\/ Draw to screen ...<br \/>\n}<br \/>\npublic void Print()<br \/>\n{<br \/>\n\/\/ Print ...<br \/>\n}<\/p>\n<p>public int GetNumberOfSides() =&gt; 4;<br \/>\n}<\/p>\n<p>Ideally, at this point you feel more comfortable with the process of defining and implementing custom interfaces using the C# syntax. To be honest, interface-based programming can take a while to get comfortable with, so if you are in fact still scratching your head just a bit, this is a perfectly normal reaction.<br \/>\nDo be aware, however, that interfaces are a fundamental aspect of the .NET Core Framework.<br \/>\nRegardless of the type of application you are developing (web-based, desktop GUIs, data access libraries, etc.), working with interfaces will be part of the process. To summarize the story thus far, remember that interfaces can be extremely useful in the following cases:<br \/>\n\u2022You have a single hierarchy where only a subset of the derived types supports a common behavior.<br \/>\n\u2022You need to model a common behavior that is found across multiple hierarchies with no common parent class beyond System.Object.<br \/>\nNow that you have drilled into the specifics of building and implementing custom interfaces, the remainder of this chapter examines several predefined interfaces contained within the .NET Core base class libraries. As you will see, you can implement standard .NET Core interfaces on your custom types to ensure they integrate into the framework seamlessly.<\/p>\n<p>The IEnumerable and IEnumerator Interfaces<br \/>\nTo begin examining the process of implementing existing .NET Core interfaces, let\u2019s first look at the role of IEnumerable and IEnumerator. Recall that C# supports a keyword named foreach that allows you to iterate over the contents of any array type.<\/p>\n<p>\/\/ Iterate over an array of items. int[] myArrayOfInts = {10, 20, 30, 40};<\/p>\n<p>foreach(int i in myArrayOfInts)<br \/>\n{<br \/>\nConsole.WriteLine(i);<br \/>\n}<\/p>\n<p>While it might seem that only array types can use this construct, the truth of the matter is any type supporting a method named GetEnumerator() can be evaluated by the foreach construct. To illustrate, begin by creating a new Console Application project named CustomEnumerator. Next, copy the Car.cs and Radio.cs files defined in the SimpleException example of Chapter 7 into the new project. Make sure to update the namespaces for the classes to CustomEnumerator.<br \/>\nNow, insert a new class named Garage that stores a set of Car objects within a System.Array. using System.Collections;<br \/>\nnamespace CustomEnumerator;<br \/>\n\/\/ Garage contains a set of Car objects. public class Garage<br \/>\n{<br \/>\nprivate Car[] carArray = new Car[4];<\/p>\n<p>\/\/ Fill with some Car objects upon startup. public Garage()<br \/>\n{<br \/>\ncarArray[0] = new Car(&quot;Rusty&quot;, 30);<\/p>\n<p>carArray[1] = new Car(&quot;Clunker&quot;, 55); carArray[2] = new Car(&quot;Zippy&quot;, 30); carArray[3] = new Car(&quot;Fred&quot;, 30);<br \/>\n}<br \/>\n}<\/p>\n<p>Ideally, it would be convenient to iterate over the Garage object\u2019s subitems using the foreach construct, just like an array of data values. Update the Program.cs file to the following:<\/p>\n<p>using System.Collections; using CustomEnumerator;<\/p>\n<p>\/\/ This seems reasonable ...<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fun with IEnumerable \/ IEnumerator <\/em><\/strong><\/strong>\\n&quot;); Garage carLot = new Garage();<\/p>\n<p>\/\/ Hand over each car in the collection? foreach (Car c in carLot)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} is going {1} MPH&quot;, c.PetName, c.CurrentSpeed);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Sadly, the compiler informs you that the Garage class does not implement a method named GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking within the System.Collections namespace.<\/p>\n<p>\u25a0Note in Chapter 10, you will learn about the role of generics and the System.Collections.Generic namespace. as you will see, this namespace contains generic versions of IEnumerable\/IEnumerator that provide a more type-safe way to iterate over items.<\/p>\n<p>Classes or structures that support this behavior advertise that they can expose contained items to the caller (in this example, the foreach keyword itself). Here is the definition of this standard interface:<\/p>\n<p>\/\/ This interface informs the caller<br \/>\n\/\/ that the object's items can be enumerated. public interface IEnumerable<br \/>\n{<br \/>\nIEnumerator GetEnumerator();<br \/>\n}<\/p>\n<p>As you can see, the GetEnumerator() method returns a reference to yet another interface named System.Collections.IEnumerator. This interface provides the infrastructure to allow the caller to traverse the internal objects contained by the IEnumerable-compatible container.<\/p>\n<p>\/\/ This interface allows the caller to<br \/>\n\/\/ obtain a container's items. public interface IEnumerator<\/p>\n<p>{<br \/>\nbool MoveNext (); \/\/ Advance the internal position of the cursor. object Current { get;} \/\/ Get the current item (read-only property). void Reset (); \/\/ Reset the cursor before the first member.<br \/>\n}<\/p>\n<p>If you want to update the Garage type to support these interfaces, you could take the long road and implement each method manually. While you are certainly free to provide customized versions of<br \/>\nGetEnumerator(), MoveNext(), Current, and Reset(), there is a simpler way. As the System.Array type (as well as many other collection classes) already implements IEnumerable and IEnumerator, you can simply delegate the request to the System.Array as follows (note you will need to import the System.Collections namespace into your code file):<\/p>\n<p>using System.Collections; namespace CustomEnumerator; public class Garage : IEnumerable<br \/>\n{<br \/>\n\/\/ System.Array already implements IEnumerator! private Car[] carArray = new Car[4];<\/p>\n<p>public Garage()<br \/>\n{<br \/>\ncarArray[0] = new Car(&quot;FeeFee&quot;, 200); carArray[1] = new Car(&quot;Clunker&quot;, 90); carArray[2] = new Car(&quot;Zippy&quot;, 30); carArray[3] = new Car(&quot;Fred&quot;, 30);<br \/>\n}<\/p>\n<p>\/\/ Return the array object's IEnumerator. public IEnumerator GetEnumerator()<br \/>\n=&gt; carArray.GetEnumerator();<br \/>\n}<\/p>\n<p>After you have updated your Garage type, you can safely use the type within the C# foreach construct. Furthermore, given that the GetEnumerator() method has been defined publicly, the object user could also interact with the IEnumerator type.<\/p>\n<p>\/\/ Manually work with IEnumerator.<br \/>\nIEnumerator carEnumerator = carLot.GetEnumerator(); carEnumerator.MoveNext();<br \/>\nCar myCar = (Car)i.Current;<br \/>\nConsole.WriteLine(&quot;{0} is going {1} MPH&quot;, myCar.PetName, myCar.CurrentSpeed);<\/p>\n<p>However, if you prefer to hide the functionality of IEnumerable from the object level, simply make use of explicit interface implementation.<\/p>\n<p>\/\/ Return the array object's IEnumerator. IEnumerator IEnumerable.GetEnumerator()<br \/>\n=&gt; return carArray.GetEnumerator();<\/p>\n<p>By doing so, the casual object user will not find the Garage\u2019s GetEnumerator() method, while the<br \/>\nforeach construct will obtain the interface in the background when necessary.<\/p>\n<p>Building Iterator Methods with the yield Keyword<br \/>\nThere is an alternative way to build types that work with the foreach loop via iterators. Simply put, an iterator is a member that specifies how a container\u2019s internal items should be returned when processed by foreach. To illustrate, create a new Console Application project named CustomEnumeratorWithYield and insert the Car, Radio, and Garage types from the previous example (again, renaming your namespace definitions to the current project). Now, retrofit the current Garage type as follows:<\/p>\n<p>public class Garage : IEnumerable<br \/>\n{<br \/>\n...<br \/>\n\/\/ Iterator method.<\/p>\n<p>public IEnumerator GetEnumerator()<br \/>\n{<br \/>\nforeach (Car c in carArray)<br \/>\n{<br \/>\nyield return c;<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>Notice that this implementation of GetEnumerator() iterates over the subitems using internal foreach logic and returns each Car to the caller using the yield return syntax. The yield keyword is used to specify the value (or values) to be returned to the caller\u2019s foreach construct. When the yield return statement is reached, the current location in the container is stored, and execution is restarted from this location the next time the iterator is called.<br \/>\nIterator methods are not required to use the foreach keyword to return its contents. It is also permissible to define this iterator method as follows:<\/p>\n<p>public IEnumerator GetEnumerator()<br \/>\n{<br \/>\nyield return carArray[0]; yield return carArray[1]; yield return carArray[2]; yield return carArray[3];<br \/>\n}<\/p>\n<p>In this implementation, notice that the GetEnumerator() method is explicitly returning a new value to the caller with each pass through. Doing so for this example makes little sense, given that if you were to add more objects to the carArray member variable, your GetEnumerator() method would now be out of sync. Nevertheless, this syntax can be useful when you want to return local data from a method for processing by the foreach syntax.<\/p>\n<p>Guard Clauses with Local Functions (New 7.0)<br \/>\nNone of the code in the GetEnumerator() method is executed until the first time that the items are iterated over (or any element is accessed). That means if there is an exception prior to the yield statement, it will not get thrown when the method is first called, but only when the first MoveNext() is called.<\/p>\n<p>To test this, update the GetEnumerator method to this:<\/p>\n<p>public IEnumerator GetEnumerator()<br \/>\n{<br \/>\n\/\/This will not get thrown until MoveNext() is called throw new Exception(&quot;This won't get called&quot;);<br \/>\nforeach (Car c in carArray)<br \/>\n{<br \/>\nyield return c;<br \/>\n}<br \/>\n}<\/p>\n<p>If you were to call the function like this and do nothing else, the exception will never be thrown:<\/p>\n<p>using System.Collections;<br \/>\n...<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fun with the Yield Keyword <\/em><\/strong><\/strong>\\n&quot;); Garage carLot = new Garage();<br \/>\nIEnumerator carEnumerator = carLot.GetEnumerator(); Console.ReadLine();<br \/>\nIt is not until MoveNext() is called that the code will execute, and the exception is thrown. Depending on the needs of your program, that might be perfectly fine. But it might not. Your GetEnumerator method might have a guard clause that needs to execute when the method is first called. For example, suppose that the list is gathered from a database. You might want to check that the database connection can be opened at the time the method is called, not when the list is iterated over. Or you might want to check the input parameters to the Iterator method (covered next) for validity.<br \/>\nRecall from Chapter 4 the C# 7 local function feature; local functions are private functions inside other functions. By moving the yield return into a local function that is returned from the main body of the method, the code in the top-level statements (before the local function is returned) is executed immediately. The local function is executed when MoveNext() is called.<br \/>\nUpdate the method to this:<\/p>\n<p>public IEnumerator GetEnumerator()<br \/>\n{<br \/>\n\/\/This will get thrown immediately<br \/>\nthrow new Exception(&quot;This will get called&quot;); return ActualImplementation();<br \/>\n\/\/this is the local function and the actual IEnumerator implementation IEnumerator ActualImplementation()<br \/>\n{<br \/>\nforeach (Car c in carArray)<br \/>\n{<br \/>\nyield return c;<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>Test this by updating the calling code to this:<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with the Yield Keyword <\/em><\/strong><\/strong>\\n&quot;); Garage carLot = new Garage();<br \/>\ntry<br \/>\n{<br \/>\n\/\/Error at this time<br \/>\nvar carEnumerator = carLot.GetEnumerator();<br \/>\n}<br \/>\ncatch (Exception e)<br \/>\n{<br \/>\nConsole.WriteLine($&quot;Exception occurred on GetEnumerator&quot;);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>With the update to the GetEnumerator() method, the exception is thrown immediately instead of when<br \/>\nMoveNext() is called.<\/p>\n<p>Building a Named Iterator<br \/>\nIt is also interesting to note that the yield keyword can technically be used within any method, regardless of its name. These methods (which are technically called named iterators) are also unique in that they can take any number of arguments. When building a named iterator, be aware that the method will return the IEnumerable interface, rather than the expected IEnumerator-compatible type. To illustrate, you could add the following method to the Garage type (using a local function to encapsulate the iteration functionality):<\/p>\n<p>public IEnumerable GetTheCars(bool returnReversed)<br \/>\n{<br \/>\n\/\/do some error checking here return ActualImplementation();<\/p>\n<p>IEnumerable ActualImplementation()<br \/>\n{<br \/>\n\/\/ Return the items in reverse. if (returnReversed)<br \/>\n{<br \/>\nfor (int i = carArray.Length; i != 0; i--)<br \/>\n{<br \/>\nyield return carArray[i - 1];<br \/>\n}<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\n\/\/ Return the items as placed in the array. foreach (Car c in carArray)<br \/>\n{<br \/>\nyield return c;<br \/>\n}<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>Notice that the new method allows the caller to obtain the subitems in sequential order, as well as in reverse order, if the incoming parameter has the value true. You could now interact with your new method as follows (be sure to comment out the throw new exception statement in the GetEnumerator() method):<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with the Yield Keyword <\/em><\/strong><\/strong>\\n&quot;); Garage carLot = new Garage();<\/p>\n<p>\/\/ Get items using GetEnumerator(). foreach (Car c in carLot)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} is going {1} MPH&quot;, c.PetName, c.CurrentSpeed);<br \/>\n}<\/p>\n<p>Console.WriteLine();<\/p>\n<p>\/\/ Get items (in reverse!) using named iterator.<br \/>\nforeach (Car c in carLot.GetTheCars(true))<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} is going {1} MPH&quot;, c.PetName, c.CurrentSpeed);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>As you might agree, named iterators are helpful constructs, in that a single custom container can define multiple ways to request the returned set.<br \/>\nSo, to wrap up this look at building enumerable objects, remember that for your custom types to work with the C# foreach keyword, the container must define a method named GetEnumerator(), which has been formalized by the IEnumerable interface type. The implementation of this method is typically achieved by simply delegating it to the internal member that is holding onto the subobjects; however, it is also possible to use the yield return syntax to provide multiple \u201cnamed iterator\u201d methods.<\/p>\n<p>The ICloneable Interface<br \/>\nAs you might recall from Chapter 6, System.Object defines a method named MemberwiseClone(). This method is used to obtain a shallow copy of the current object. Object users do not call this method directly, as it is protected. However, a given object may call this method itself during the cloning process. To illustrate, create a new Console Application project named CloneablePoint that defines a class named Point.<\/p>\n<p>namespace CloneablePoint;<br \/>\n\/\/ A class named Point. public class Point<br \/>\n{<br \/>\npublic int X {get; set;} public int Y {get; set;}<\/p>\n<p>public Point(int xPos, int yPos) { X = xPos; Y = yPos;} public Point(){}<\/p>\n<p>\/\/ Override Object.ToString().<br \/>\npublic override string ToString() =&gt; $&quot;X = {X}; Y = {Y}&quot;;<br \/>\n}<\/p>\n<p>Given what you already know about reference types and value types (see Chapter 4), you are aware that if you assign one reference variable to another, you have two references pointing to the same object in<br \/>\nmemory. Thus, the following assignment operation results in two references to the same Point object on the heap; modifications using either reference affect the same object on the heap:<\/p>\n<p>using CloneablePoint;<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fun with Object Cloning <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n\/\/ Two references to same object! Point p1 = new Point(50, 50); Point p2 = p1;<br \/>\np2.X = 0;<br \/>\nConsole.WriteLine(p1); Console.WriteLine(p2); Console.ReadLine();<\/p>\n<p>When you want to give your custom type the ability to return an identical copy of itself to the caller, you may implement the standard ICloneable interface. As shown at the start of this chapter, this type defines a single method named Clone().<\/p>\n<p>public interface ICloneable<br \/>\n{<br \/>\nobject Clone();<br \/>\n}<\/p>\n<p>Obviously, the implementation of the Clone() method varies among your classes. However, the basic functionality tends to be the same: copy the values of your member variables into a new object instance of the same type and return it to the user. To illustrate, ponder the following update to the Point class:<\/p>\n<p>\/\/ The Point now supports &quot;clone-ability.&quot; public class Point : ICloneable<br \/>\n{<br \/>\npublic int X { get; set; } public int Y { get; set; }<\/p>\n<p>public Point(int xPos, int yPos) { X = xPos; Y = yPos; } public Point() { }<\/p>\n<p>\/\/ Override Object.ToString().<br \/>\npublic override string ToString() =&gt; $&quot;X = {X}; Y = {Y}&quot;;<\/p>\n<p>\/\/ Return a copy of the current object.<br \/>\npublic object Clone() =&gt; new Point(this.X, this.Y);<br \/>\n}<\/p>\n<p>In this way, you can create exact stand-alone copies of the Point type, as illustrated by the following code:<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Object Cloning <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\n\/\/ Notice Clone() returns a plain object type.<br \/>\n\/\/ You must perform an explicit cast to obtain the derived type. Point p3 = new Point(100, 100);<br \/>\nPoint p4 = (Point)p3.Clone();<\/p>\n<p>\/\/ Change p4.X (which will not change p3.X). p4.X = 0;<\/p>\n<p>\/\/ Print each object.<br \/>\nConsole.WriteLine(p3);<br \/>\nConsole.WriteLine(p4);<br \/>\nConsole.ReadLine();<\/p>\n<p>While the current implementation of Point fits the bill, you can streamline things just a bit. Because the Point type does not contain any internal reference type variables, you could simplify the implementation of the Clone() method as follows:<\/p>\n<p>\/\/ Copy each field of the Point member by member. public object Clone() =&gt; this.MemberwiseClone();<\/p>\n<p>Be aware, however, that if the Point did contain any reference type member variables, MemberwiseClone() would copy the references to those objects (i.e., a shallow copy). If you want to support a true deep copy, you will need to create a new instance of any reference type variables during the cloning process. Let\u2019s see an example next.<\/p>\n<p>A More Elaborate Cloning Example<br \/>\nNow assume the Point class contains a reference type member variable of type PointDescription. This class maintains a point\u2019s friendly name as well as an identification number expressed as a System.Guid (a globally unique identifier [GUID] is a statistically unique 128-bit number). Here is the implementation:<\/p>\n<p>namespace CloneablePoint;<br \/>\n\/\/ This class describes a point. public class PointDescription<br \/>\n{<br \/>\npublic string PetName {get; set;} public Guid PointID {get; set;}<\/p>\n<p>public PointDescription()<br \/>\n{<br \/>\nPetName = &quot;No-name&quot;; PointID = Guid.NewGuid();<br \/>\n}<br \/>\n}<\/p>\n<p>The initial updates to the Point class itself included modifying ToString() to account for these new bits of state data, as well as defining and creating the PointDescription reference type. To allow the outside world to establish a pet name for the Point, you also update the arguments passed into the overloaded constructor.<\/p>\n<p>public class Point : ICloneable<br \/>\n{<br \/>\npublic int X { get; set; } public int Y { get; set; }<br \/>\npublic PointDescription desc = new PointDescription();<\/p>\n<p>public Point(int xPos, int yPos, string petName)<br \/>\n{<br \/>\nX = xPos; Y = yPos; desc.PetName = petName;<br \/>\n}<br \/>\npublic Point(int xPos, int yPos)<br \/>\n{<br \/>\nX = xPos; Y = yPos;<br \/>\n}<br \/>\npublic Point() { }<\/p>\n<p>\/\/ Override Object.ToString(). public override string ToString()<br \/>\n=&gt; $&quot;X = {X}; Y = {Y}; Name = {desc.PetName};\\nID = {desc.PointID}\\n&quot;;<\/p>\n<p>\/\/ Return a copy of the current object.<br \/>\npublic object Clone() =&gt; this.MemberwiseClone();<br \/>\n}<\/p>\n<p>Notice that you did not yet update your Clone() method. Therefore, when the object user asks for a clone using the current implementation, a shallow (member-by-member) copy is achieved. To illustrate, assume you have updated the calling code as follows:<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Object Cloning <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\nConsole.WriteLine(&quot;Cloned p3 and stored new Point in p4&quot;); Point p3 = new Point(100, 100, &quot;Jane&quot;);<br \/>\nPoint p4 = (Point)p3.Clone();<\/p>\n<p>Console.WriteLine(&quot;Before modification:&quot;); Console.WriteLine(&quot;p3: {0}&quot;, p3);<br \/>\nConsole.WriteLine(&quot;p4: {0}&quot;, p4); p4.desc.PetName = &quot;My new Point&quot;; p4.X = 9;<\/p>\n<p>Console.WriteLine(&quot;\\nChanged p4.desc.petName and p4.X&quot;); Console.WriteLine(&quot;After modification:&quot;); Console.WriteLine(&quot;p3: {0}&quot;, p3);<br \/>\nConsole.WriteLine(&quot;p4: {0}&quot;, p4); Console.ReadLine();<\/p>\n<p>Notice in the following output that while the value types have indeed been changed, the internal reference types maintain the same values, as they are \u201cpointing\u201d to the same objects in memory (specifically, note that the pet name for both objects is now \u201cMy new Point\u201d).<\/p>\n<p><strong><strong><em> Fun with Object Cloning <\/em><\/strong><\/strong> Cloned p3 and stored new Point in p4 Before modification:<br \/>\np3: X = 100; Y = 100; Name = Jane;<br \/>\nID = 133d66a7-0837-4bd7-95c6-b22ab0434509<\/p>\n<p>p4: X = 100; Y = 100; Name = Jane;<br \/>\nID = 133d66a7-0837-4bd7-95c6-b22ab0434509<\/p>\n<p>Changed p4.desc.petName and p4.X After modification:<br \/>\np3: X = 100; Y = 100; Name = My new Point; ID = 133d66a7-0837-4bd7-95c6-b22ab0434509<\/p>\n<p>p4: X = 9; Y = 100; Name = My new Point; ID = 133d66a7-0837-4bd7-95c6-b22ab0434509<\/p>\n<p>To have your Clone() method make a complete deep copy of the internal reference types, you need to configure the object returned by MemberwiseClone() to account for the current point\u2019s name (the System.Guid type is in fact a structure, so the numerical data is indeed copied). Here is one possible implementation:<\/p>\n<p>\/\/ Now we need to adjust for the PointDescription member. public object Clone()<br \/>\n{<br \/>\n\/\/ First get a shallow copy.<br \/>\nPoint newPoint = (Point)this.MemberwiseClone();<\/p>\n<p>\/\/ Then fill in the gaps.<br \/>\nPointDescription currentDesc = new PointDescription(); currentDesc.PetName = this.desc.PetName;<br \/>\nnewPoint.desc = currentDesc; return newPoint;<br \/>\n}<\/p>\n<p>If you rerun the application once again and view the output (shown next), you see that the Point returned from Clone() does copy its internal reference type member variables (note the pet name is now unique for both p3 and p4).<\/p>\n<p><strong><strong><em> Fun with Object Cloning <\/em><\/strong><\/strong> Cloned p3 and stored new Point in p4 Before modification:<br \/>\np3: X = 100; Y = 100; Name = Jane;<br \/>\nID = 51f64f25-4b0e-47ac-ba35-37d263496406<\/p>\n<p>p4: X = 100; Y = 100; Name = Jane;<br \/>\nID = 0d3776b3-b159-490d-b022-7f3f60788e8a<\/p>\n<p>Changed p4.desc.petName and p4.X After modification:<br \/>\np3: X = 100; Y = 100; Name = Jane;<br \/>\nID = 51f64f25-4b0e-47ac-ba35-37d263496406<\/p>\n<p>p4: X = 9; Y = 100; Name = My new Point; ID = 0d3776b3-b159-490d-b022-7f3f60788e8a<\/p>\n<p>To summarize the cloning process, if you have a class or structure that contains nothing but value types, implement your Clone() method using MemberwiseClone(). However, if you have a custom type that maintains other reference types, you might want to create a new object that considers each reference type member variable to get a \u201cdeep copy.\u201d<\/p>\n<p>The IComparable Interface<br \/>\nThe System.IComparable interface specifies a behavior that allows an object to be sorted based on some specified key. Here is the formal definition:<\/p>\n<p>\/\/ This interface allows an object to specify its<br \/>\n\/\/ relationship between other like objects. public interface IComparable<br \/>\n{<br \/>\nint CompareTo(object o);<br \/>\n}<\/p>\n<p>\u25a0Note The generic version of this interface (IComparable<T>) provides a more type-safe manner to handle comparisons between objects. You will examine generics in Chapter 10.<\/p>\n<p>Create a new Console Application project named ComparableCar, copy the Car and Radio classes from the SimpleException example in Chapter 7, and rename the namespace for each file to ComparableCar.<br \/>\nUpdate the Car class by adding a new property to represent a unique ID for each car and a modified constructor:<\/p>\n<p>using System.Collections;<\/p>\n<p>namespace ComparableCar; public class Car<br \/>\n{<br \/>\n.<br \/>\npublic int CarID {get; set;}<br \/>\npublic Car(string name, int currSp, int id)<br \/>\n{<\/p>\n<p>}<br \/>\n...<br \/>\n}<\/p>\n<p>CurrentSpeed = currSp;<br \/>\nPetName = name;<br \/>\nCarID = id;<\/p>\n<p>Now assume you have an array of Car objects as follows in your top-level statements:<br \/>\nglobal using System.Collections; using ComparableCar;<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fun with Object Sorting <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>\/\/ Make an array of Car objects. Car[] myAutos = new Car[5]; myAutos[0] = new Car(&quot;Rusty&quot;, 80, 1);<br \/>\nmyAutos[1] = new Car(&quot;Mary&quot;, 40, 234);<br \/>\nmyAutos[2] = new Car(&quot;Viper&quot;, 40, 34);<br \/>\nmyAutos[3] = new Car(&quot;Mel&quot;, 40, 4);<br \/>\nmyAutos[4] = new Car(&quot;Chucky&quot;, 40, 5); Console.ReadLine();<br \/>\nThe System.Array class defines a static method named Sort(). When you invoke this method on an array of intrinsic types (int, short, string, etc.), you can sort the items in the array in numeric\/alphabetic order, as these intrinsic data types implement IComparable. However, what if you were to send an array of Car types into the Sort() method as follows?<\/p>\n<p>\/\/ Sort my cars? Not yet! Array.Sort(myAutos);<\/p>\n<p>If you run this test, you would get a runtime exception, as the Car class does not support the necessary interface. When you build custom types, you can implement IComparable to allow arrays of your types to be sorted. When you flesh out the details of CompareTo(), it will be up to you to decide what the baseline of the ordering operation will be. For the Car type, the internal CarID seems to be the logical candidate.<\/p>\n<p>\/\/ The iteration of the Car can be ordered<br \/>\n\/\/ based on the CarID.<br \/>\npublic class Car : IComparable<br \/>\n{<br \/>\n...<br \/>\n\/\/ IComparable implementation.<br \/>\nint IComparable.CompareTo(object obj)<br \/>\n{<br \/>\nif (obj is Car temp)<br \/>\n{<br \/>\nif (this.CarID &gt; temp.CarID)<br \/>\n{<br \/>\nreturn 1;<br \/>\n}<br \/>\nif (this.CarID &lt; temp.CarID)<br \/>\n{<br \/>\nreturn -1;<br \/>\n}<br \/>\nreturn 0;<br \/>\n}<br \/>\nthrow new ArgumentException(&quot;Parameter is not a Car!&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>As you can see, the logic behind CompareTo() is to test the incoming object against the current instance based on a specific point of data. The return value of CompareTo() is used to discover whether this type is less than, greater than, or equal to the object it is being compared with (see Table 8-1).<\/p>\n<p>Table 8-1. CompareTo Return Values<\/p>\n<p>Return Value    Description<br \/>\nAny number less than zero   This instance comes before the specified object in the sort order.<br \/>\nZero    This instance is equal to the specified object.<br \/>\nAny number greater than zero    This instance comes after the specified object in the sort order.<\/p>\n<p>You can streamline the previous implementation of CompareTo() given that the C# int data type (which is just a shorthand notation for System.Int32) implements IComparable. You could implement the Car\u2019s CompareTo() as follows:<\/p>\n<p>int IComparable.CompareTo(object obj)<br \/>\n{<br \/>\nif (obj is Car temp)<br \/>\n{<br \/>\nreturn this.CarID.CompareTo(temp.CarID);<br \/>\n}<br \/>\nthrow new ArgumentException(&quot;Parameter is not a Car!&quot;);<br \/>\n}<\/p>\n<p>In either case, so that your Car type understands how to compare itself to like objects, you can write the following user code:<\/p>\n<p>\/\/ Exercise the IComparable interface.<br \/>\n\/\/ Make an array of Car objects.<br \/>\n...<br \/>\n\/\/ Display current array.<br \/>\nConsole.WriteLine(&quot;Here is the unordered set of cars:&quot;); foreach(Car c in myAutos)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} {1}&quot;, c.CarID, c.PetName);<br \/>\n}<\/p>\n<p>\/\/ Now, sort them using IComparable! Array.Sort(myAutos); Console.WriteLine();<\/p>\n<p>\/\/ Display sorted array.<br \/>\nConsole.WriteLine(&quot;Here is the ordered set of cars:&quot;); foreach(Car c in myAutos)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} {1}&quot;, c.CarID, c.PetName);<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Here is the output from the previous code listing:<\/p>\n<p><strong><strong><em> Fun with Object Sorting <\/em><\/strong><\/strong> Here is the unordered set of cars:<br \/>\n1 Rusty<br \/>\n234 Mary<br \/>\n34 Viper<br \/>\n4Mel<br \/>\n5Chucky<\/p>\n<p>Here is the ordered set of cars:<br \/>\n1 Rusty<br \/>\n4Mel<br \/>\n5Chucky<br \/>\n34 Viper<br \/>\n234 Mary<\/p>\n<p>Specifying Multiple Sort Orders with IComparer<br \/>\nIn this version of the Car type, you used the car\u2019s ID as the base for the sort order. Another design might have used the pet name of the car as the basis for the sorting algorithm (to list cars alphabetically). Now, what if you wanted to build a Car that could be sorted by ID as well as by pet name? If this is the type of behavior you are interested in, you need to make friends with another standard interface named IComparer, defined within the System.Collections namespace as follows:<\/p>\n<p>\/\/ A general way to compare two objects. interface IComparer<br \/>\n{<br \/>\nint Compare(object o1, object o2);<br \/>\n}<\/p>\n<p>\u25a0Note The generic version of this interface (IComparer<T>) provides a more type-safe manner to handle comparisons between objects. You will examine generics in Chapter 10.<\/p>\n<p>Unlike the IComparable interface, IComparer is typically not implemented on the type you are trying to sort (i.e., the Car). Rather, you implement this interface on any number of helper classes, one for each sort order (pet name, car ID, etc.). Currently, the Car type already knows how to compare itself against other cars based on the internal car ID. Therefore, allowing the object user to sort an array of Car objects by pet name will require an additional helper class that implements IComparer. Here is the code:<\/p>\n<p>namespace ComparableCar;<br \/>\n\/\/ This helper class is used to sort an array of Cars by pet name. public class PetNameComparer : IComparer<br \/>\n{<br \/>\n\/\/ Test the pet name of each object.<br \/>\nint IComparer.Compare(object o1, object o2)<br \/>\n{<\/p>\n<p>if (o1 is Car t1 &amp;&amp; o2 is Car t2)<br \/>\n{<br \/>\nreturn string.Compare(t1.PetName, t2.PetName, StringComparison.OrdinalIgnoreCase);<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nthrow new ArgumentException(&quot;Parameter is not a Car!&quot;);<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>The object user code can use this helper class. System.Array has several overloaded Sort() methods, one that just happens to take an object implementing IComparer.<\/p>\n<p>...<br \/>\n\/\/ Now sort by pet name. Array.Sort(myAutos, new PetNameComparer());<\/p>\n<p>\/\/ Dump sorted array. Console.WriteLine(&quot;Ordering by pet name:&quot;); foreach(Car c in myAutos)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} {1}&quot;, c.CarID, c.PetName);<br \/>\n}<br \/>\n...<\/p>\n<p>Custom Properties and Custom Sort Types<br \/>\nIt is worth pointing out that you can use a custom static property to help the object user along when sorting your Car types by a specific data point. Assume the Car class has added a static read-only property named SortByPetName that returns an instance of an object implementing the IComparer interface (PetNameComparer, in this case; be sure to import System.Collections).<\/p>\n<p>\/\/ We now support a custom property to return<br \/>\n\/\/ the correct IComparer interface. public class Car : IComparable<br \/>\n{<br \/>\n...<br \/>\n\/\/ Property to return the PetNameComparer. public static IComparer SortByPetName<br \/>\n=&gt; (IComparer)new PetNameComparer();}<\/p>\n<p>The object user code can now sort by pet name using a strongly associated property, rather than just \u201chaving to know\u201d to use the stand-alone PetNameComparer class type.<\/p>\n<p>\/\/ Sorting by pet name made a bit cleaner. Array.Sort(myAutos, Car.SortByPetName);<\/p>\n<p>Ideally, at this point you not only understand how to define and implement your own interfaces but also understand their usefulness. To be sure, interfaces are found within every major .NET Core namespace, and you will continue working with various standard interfaces in the remainder of this book.<\/p>\n<p>Summary<br \/>\nAn interface can be defined as a named collection of abstract members. It is common to regard an interface as a behavior that may be supported by a given type. When two or more classes implement the same interface, you can treat each type the same way (interface-based polymorphism) even if the types are defined within unique class hierarchies.<br \/>\nC# provides the interface keyword to allow you to define a new interface. As you have seen, a type can support as many interfaces as necessary using a comma-delimited list. Furthermore, it is permissible to build interfaces that derive from multiple base interfaces.<br \/>\nIn addition to building your custom interfaces, the .NET Core libraries define several standard (i.e., framework-supplied) interfaces. As you have seen, you are free to build custom types that implement these predefined interfaces to gain several desirable traits such as cloning, sorting, and enumerating.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>PART IV Advanced C# Programming CHAPTER 8 Working with Interfaces This chapter builds upon your current understanding of object-oriented development by examining the topic of interface-based programming. Here, you will learn how to define and implement interfaces and come to understand the benefits of building types that support multiple behaviors. Along the way, you will [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[22],"class_list":["post-304","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-pro-csharp10-with-net6"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=304"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/304\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}