📜 ⬆️ ⬇️

A bit about the interfaces in .Net (based on one interview)

Last Monday, I was lucky to get an interview at Senior .Net Developer in one international company. During the interview I was offered to take a test where a number of questions were related to .Net. In particular, in one of the questions it was necessary to give an assessment (truth / false) to a number of statements, among which were:

In .Net, any array of elements, for example, int [], implements an IList by default, which allows it to be used as a collection in a foreach statement.


Quickly answering this question in the negative and separately added to the fields. that foreach requires an implementation not of an IList, but of an IEnumerable, I moved on to the next question. However, on the way home I was tormented by the question: does the array implement this interface anyway or not?
')
About IList, I vaguely remembered that this interface gives me an IEnumerable, an indexer and a Count property that contains the number of elements in the collection, as well as a couple of rarely used properties, such as IsFixedCollection (). The array has the Length property for its size, and the Count in IEnumerable is an extension method from LINQ, which would be impossible if this method were implemented in a class. Thus, it turned out that the array could not implement the IList interface, but some vague feeling did not give me rest. Therefore, in the evening after the interview, I decided to conduct a small study.


Class System.Array


Since Reflector.Net was not installed with me, I simply wrote a short C # program to find out what interfaces are implemented by an integer array.

var v = new int[] { 1, 2, 3 }; var t = v.GetType(); var i = t.GetInterfaces(); foreach(var tp in i) Console.WriteLine(tp.Name); 


Here is the complete list of interfaces received from the console window:

 ICloneable IList ICollection IEnumerable IStructuralComparable IStructuralEquatable IList`1 ICollection`1 IEnumerable`1 IReadOnlyList`1 IReadOnlyCollection`1 


Thus, an array in .Net still implements the IList interface and its generic version of IList <> .

To get more complete information, I built a system.Array class diagram.



My mistake immediately caught my eye: Count was a property not of IList, but of ICollection, the previous interface in the inheritance chain. However, the array itself no longer had such a property as many other properties of the IList interface, although other properties of this interface, IsFixedSize and IsReadOnly, were implemented. How is that even possible?

Everything immediately falls into place when you remember that in C # you can implement interfaces not only
implicitly, but explicitly. I knew about this possibility from textbooks, where an example of such an implementation in the case was cited. when the base class already contains a method with the same name as the interface method. I also saw this feature in ReSharper. However, to date, I have not directly encountered the need to explicitly implement interfaces in my own projects.

Comparing explicit and implicit interface implementations


Let's compare these two types of interface implementations:

Criteria
Implicit implementation
Explicit implementation
Basic syntax
 interface ITest { void DoTest(); } public class ImplicitTest : ITest { public void DoTest() { } } 

 interface ITest { void DoTest(); } public class ExplicitTest : ITest { void ITest.DoTest() { } } 


Visibility
Implicit implementation has always been open (public), so methods and properties can be accessed directly.
 var imp = new ImplicitTest(); imp.DoTest(); 

Explicit implementation is always closed.
To access the implementation, you need to cast an instance of the class to the interface (upcast to interface).
 var exp = new ExplicitTest(); ((ITest)exp).DoTest(); 

Polymorphia
The implicit implementation of an interface can be virtual (virtual), which allows rewriting this implementation in descendant classes.
Explicit implementation is always static. It cannot be rewritten (override) or overlapped (new) in descendant classes. Note one
Abstract class and implementation
An implicit implementation can be abstract and can only be implemented in a descendant class.
An explicit implementation cannot be abstract, but the class itself can have other abstract methods and itself be abstract. Note 2

Notes:
Note 1 - As mayorovp rightly notes in the comments, the implementation can be redefined when the interface is re-explicitly implemented in the descendant class (see the first comment on the article).

Note 2 - In one of the blogs indicated that the class itself can not be abstract. Perhaps this was true for some of the previous versions of the compiler, in my experiments I could easily implement the interface explicitly in an abstract class.

Why do we need an explicit interface implementation


An explicit interface implementation, according to MSDN , is necessary in the case when several interfaces implemented by a class have a method with the same signature. This problem in its general form is known in the English-speaking world under the chilling name "deadly diamond of death" , which translates into Russian as the "diamond pattern" . Here is an example of such a situation:

 /* Listing 1 */ interface IJogger { void Run(); } interface ISkier { void Run(); } public class Athlete: ISkier, IJogger { public void Run() { Console.WriteLine("Am I an Athlete, Skier or Jogger?"); } } 


By the way, this example is the correct code in C #, that is, it is (correctly) compiled and run, while the Run () method is both a method of the class itself and an implementation of two interfaces. Thus, we can have one implementation for different interfaces and for the class itself. You can check it with the following code:

 /* Listing 2 */ var sp = new Athlete(); sp.Run(); (sp as ISkier).Run(); (sp as IJogger).Run(); 


The result of the execution of this code will be “Am I an Athlete, Skier or Jogger?” , Displayed in the console three times.

This is where we can use an explicit interface implementation to separate all three cases:

 /* Listing 3 */ public class Sportsman { public virtual void Run() { Console.WriteLine("I am a Sportsman"); } } public class Athlete: Sportsman, ISkier, IJogger { public override void Run() { Console.WriteLine("I am an Athlete"); } void ISkier.Run() { Console.WriteLine("I am a Skier"); } void IJogger.Run() { Console.WriteLine("I am a Jogger"); } } 


In this case, when executing the code from Listing 2, we see three lines in the console, “I am an Athlete” , “I am a Skier” and “I am a Jogger” .

Pros and cons of various interface implementations


Implementation visibility and sample implementation

As shown above, the implicit implementation is not syntactically different from the usual class method (and if this method has already been defined in the ancestor class, then in this syntax the method will be hidden (hidden) in the children and the code will be compiled without problems c compiler warning about hiding a method.). Moreover, it is possible to selectively implement individual methods of a single interface both explicitly and implicitly:

 /* Listing 4 */ public class Code { public void Run() { Console.WriteLine("I am a class method"); } } interface ICommand { void Run(); void Execute(); } public class CodeCommand : Code, ICommand { // implicit interface method implementation // => public implementation // implicit base class method hiding (warning here) public void Run() { base.Run(); } // explicit interface method implementation // => private implementation void ICommand.Execute() {} } 


This allows you to use implementations of individual interface methods as native class methods and they are available, for example, through IntelliSense, in contrast to the explicit implementation of methods that are private and visible only after the cast to the corresponding interface.

On the other hand, the possibility of private implementation of methods allows you to hide a number of interface methods, while fully implementing it. Returning to our very first example with arrays in .Net, you can see that the array hides, for example, the implementation of the Count property of the ICollection interface, exposing this property under the name Length (probably this is an attempt to maintain compatibility with C ++ STL and Java). Thus, we can hide certain methods of the implemented interface and not hide (= make public) others.

Here, however, such a problem arises that in many cases it is completely impossible to guess which interfaces are implemented by the class “implicitly”, since neither the methods nor the properties of these interfaces are visible in IntelliSense (the example with System.Array is also indicative). The only way to identify such implementations is to use reflection, for example, using the Object Browser in Visual Studio.

Interface refactoring

Since the implicit (public) implementation of the interface does not differ from the implementation of the class's public method, if the interface is refactored and any public method is removed from it (for example, when combining the Run () and Execute () methods from the above ICommand interface into a single Run method ( )) In all implicit implementations, the open access method will remain, which is very likely to be maintained even after refactoring, since this public method may already have different dependencies in other components of the system. As a result, the principle of programming “against interfaces, not implementations” will be violated, since dependencies will already be between specific (and in different classes, probably different) implementations of the former interface method.

 /* Listing 5 */ interface IFingers { void Thumb(); void IndexFinger(); // an obsolete interface method // void MiddleFinger(); } public class HumanPalm : IFingers { public void Thumb() {} public void IndexFinger() {} // here is a "dangling" public method public void MiddleFinger() {} } public class AntropoidHand : IFingers { void IFingers.Thumb() {} void IFingers.IndexFinger() {} // here the compiler error void IFingers.MiddleFinger() {} } 


In the case of private implementation of interfaces, all classes with an explicit implementation of a nonexistent method will simply cease to compile, but after removing the implementation that has become unnecessary (or refactoring it to a new method), we will not have an “extra” public method that is not tied to any interface. Of course, it may be necessary to refactor dependencies on the interface itself, but here, at least, there will be no violation of the “program to interfaces, not implementations” principle.

As for properties, implicitly implemented interface properties (properties) allow them to be accessed via accessor methods (getter and setter) both from the outside and directly from the class itself, which can lead to unnecessary effects (for example, to unnecessary data validation during initialization). properties).

 /* Listing 6 */ interface IProperty { int Amount { get; set; } } public class ClassWithProperty : IProperty { // implicit implementation, public public int Amount { get; set; } public ClassWithProperty() { // internal invocation of the public setter Amount = 1000; } } public class ClassWithExplicitProperty : IProperty { // explicit implementation, private int IProperty.Amount { get; set; } public ClassWithExplicitProperty() { // internal invocation isn't possible // compiler error here Amount = 1000; } } 


With the explicit implementation of interface properties, these properties remain private and for access you have to go a “long” way and declare an additional private field, through which initialization takes place. As a result, this leads to cleaner code when property access methods are used only for access from the outside.

Using explicit typing of local variables and class fields

In the case of an explicit implementation of interfaces, we have to explicitly indicate that we are working not with an instance of a class, but with an instance of an interface. Thus, for example, it becomes impossible to use type inference and the declaration of local variables in C # using the service word var. Instead, we have to use an explicit declaration specifying the type of interface when declaring local variables, as well as in the method signature and in the class fields.

Thus, on the one hand, we make the code somewhat less flexible (for example, ReSharper, by default, always suggests using a declaration with var if possible), but avoid potential problems associated with binding to a specific implementation, as the system grows code. This point may seem controversial to many, but in the case when several people work on the project, and even in different parts of the world, using explicit typing can be very useful, since it increases the readability of the code and reduces the cost of supporting it.

Related Sources
In preparing the article, information from a number of network sources was used, in particular from blogs ( [1] , [2] , [3] and [4] ), as well as from [5] and [6] questions from StackOverflow, a very interesting article on CodeProject and chapters 13.5 of the book Jeffrey Richter " CLR via C # ".
Small bonus: two questions on backfill (for the inquisitive)
These questions are not directly related to the topic of explicit implementation of interfaces, but it seems to me that here they may be of interest to someone:
1. If one more line is assigned to Listing 2
 (sp as Sportsman).Run(); 

What will be displayed in the console?

2. How, using the minimal change in Listing 3 (replacing one keyword with another), achieve the console output of the phrase “I am a Sportsman” in the first question?

Source: https://habr.com/ru/post/156037/


All Articles