An “interface program” using extension methods: when does it go too far?

Background: In the spirit of “a program for an interface, not an implementation” and Haskell class classes , and as an encoding experiment, I think about what it means to create an API based mainly on a combination of interfaces and extension methods. I have two principles:

  • Avoid class inheritance whenever possible. Interfaces should be implemented as sealed class es.
    (This happens for two reasons: firstly, because the subclassification raises some unpleasant questions about how to specify and apply a base class contract in its derived classes. Secondly, and what the Haskell class does, polymorphism does not require a subclass.) sub>

  • Avoid instance methods where possible. If this can be done using extension methods, this is preferable.
    (This is intended to ensure the compactness of the interfaces: everything that can be done using a combination of other instance methods becomes an extension method. What remains in the interface are the basic functions and, in particular, state change methods). Sub>

Problem: I am having problems with the second guide. Consider this:

 interface IApple { } static void Eat(this IApple apple) { Console.WriteLine("Yummy, that was good!"); } interface IRottenApple : IApple { } static void Eat(this IRottenApple apple) { Console.WriteLine("Eat it yourself, you disgusting human, you!"); } sealed class RottenApple : IRottenApple { } IApple apple = new RottenApple(); // API user might expect virtual dispatch to happen (as usual) when 'Eat' is called: apple.Eat(); // ==> "Yummy, that was good!" 

Obviously, for the expected result ( "Eat it yourself…" ), Eat should be the usual instance method.

Question:. What would be a refined / more accurate guide to using extension methods or virtual instance methods? When does using extension methods for “programming for an interface” go too far? When are instance methods really needed?

I do not know if there is any clear general rule, so I do not expect a perfect universal answer. Any well-substantiated improvements to guideline (2) are appreciated above.

+6
source share
3 answers

Your guide is good enough as it is: it already says "wherever possible." Therefore, the task is to describe the bit “whenever possible” in some detail.

I use this simple dichotomy: if the purpose of adding a method is to hide the differences between subclasses, use the extension method; if the goal is to highlight the differences, use the virtual method.

Your Eat method is an example of a method that introduces the difference between subclasses: the process of eating (or not) an apple depends on what kind of apple it is. Therefore, you must implement it as an instance method.

An example of a method that tries to hide the differences would be ThrowAway :

 public static void ThrowAway(this IApple apple) { var theBin = RecycleBins.FindCompostBin(); if (theBin != null) { theBin.Accept(apple); return; } apple.CutUp(); RecycleBins.FindGarbage().Accept(apple); } 

If the process of ejecting the apple is the same regardless of the type of apple, the operation is the main candidate for implementation in the expansion method.

+6
source

For me, the expected result was correct. You enter a type (possibly using it incorrectly) of a variable as IApple.

For instance:

 IApple apple = new RottenApple(); apple.Eat(); // "Yummy, that was good!" IRottenApple apple2 = new RottenApple(); apple2.Eat(); // "Eat it yourself, you disgusting human, you!" var apple3 = new RottenApple(); apple.Eat(); // "Eat it yourself, you disgusting human, you!" 

Question: What would be a refined / more accurate guide to using extension methods or virtual instances? When does using extension methods for "programming in an interface" go far? When are instance methods really needed?

Just my personal opinion when developing the application:

I use instance methods when I write something that I can or someone else can use. This is because it is a requirement for what type it really is. Consider the FlyingObject interface / class using the Fly() method. This is the basic fundamental method of a flying object. Creating an extension method really doesn't make sense.

I use (many) extension methods, but this is never a requirement for using the class they distribute. For example, I have an extension method to int that creates a SqlParameter (optionally, it is internal). However, it makes no sense to have this method as part of the int base class; it really has nothing to do with what int does or does. An extension method is a visually good way to create a reusable method that consumes a class / structure.

+1
source

I noticed that C # extension methods can be very similar to non-member functions other than C ++, as follows: Both Scott Myers and Herb Sutter argue that in C ++ encapsulation sometimes increases without making the function a member of the class:

"Where possible, prefer to write functions as unsupported friends." - > Summary of Herb Sutter GotW # 84

(Sutter justifies this approach in his article on the principle of interface .)

Back in 1991, Scott Meyers even formulated an algorithm to determine if a function should be a member function, a friend function, or a non-member function different from each other:

if ( f must be virtual)
& emsp; & emsp; make f is a member function of C ;
else if ( f - operator>> or operator<< )
& emsp; & emsp; make f a non-member function;
& emsp; & emsp; if ( f needs access to non-public members of C )
& emsp; & emsp; & emsp; & emsp; make f friend of C ;
else if ( f requires type conversion in its left-most argument)
& emsp; & emsp; make f a non-member function;
& emsp; & emsp; if ( f needs access to non-public members of C )
& emsp; & emsp; & emsp; & emsp; make f friend of C ;
else if ( f can be implemented via the C open interface)
& emsp; & emsp; make f a non-member function;
yet
& emsp; & emsp; make f is a member function of C ;

- Scott Myers expanded the algorithm since 1998 (reformatted)

Some of them are clearly specific for C ++, but it is fairly easy to find a similar algorithm for C #. (In the beginning, friend can be approximated in C # with an internal access modifier, "non-member functions" can be either extension methods or other static methods.)

What this algorithm does not say is when, or why, f "must be virtual." @dasblinkenlight's answer goes quite a bit to explain this bit.


Actual questions about stack overflow:

0
source

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


All Articles