21

As a C++ developer I'm quite used to C++ header files, and find it beneficial to have some kind of forced "documentation" inside the code. I usually have a bad time when I have to read some C# code because of that: I don't have that sort of mental map of the class I'm working with.

Let's assume that as a software engineer I'm designing a program's framework. Would it be too crazy to define every class as an abstract unimplemented class, similarly to what we would do with C++ headers, and let developers implement it?

I'm guessing there may be some reasons why someone could find this to be a terrible solution but I'm not sure why. What would one have to consider for a solution like this?

  • 13
    C# is a totally different programming language than C++. Some syntax looks similar, but you need to understand C# to do good work in it. And you should do something about the bad habit you have with relying on header files. I've worked with C++ for decades, I've never read the header files, only written them. – Bent Jan 24 '18 at 15:11
  • 17
    One of the best things of C# and Java is the freedom from header files! Just use a good IDE. – Erik Eidt Jan 24 '18 at 15:14
  • 9
    Declarations in C++ header files don't end up being part of the generated binary. They are there for the compiler and linker. These C# abstract classes would be part of the generated code, to no benefit whatsoever. – Mark Benningfield Jan 24 '18 at 15:25
  • 16
    The equivalent construct in C# is an interface, not an abstract class. – Robert Harvey Jan 24 '18 at 15:55
  • 6
    @DaniBarcaCasafont "I see headers as a fast and reliable way to see what a class methods and attributes are, what parameter types does it expect and what it returns". When using Visual Studio my alternative to that is Ctrl-M-O shortcut. – wha7ever - Reinstate Monica Jan 24 '18 at 18:58
44

The reason this was done in C++ had to do with making compilers faster and easier to implement. This was not a design to make programming easier.

The purpose of the header files was to enable the compiler to do a super quick first pass to know all the expected function names and allocate memory locations for them so that they can be referenced when called in cp files, even if the class defining them had not been parsed yet.

Trying to replicate a consequence of old hardware limitations in a modern development environment is not recommended!

Defining a interface or abstract class for every class will lower your productivity; what else could you have done with that time? Also, other developers will not follow this convention.

In fact, other developers might delete your abstract classes. If I find an interface in code that meets both of these criteria, I delete it and refactor it out of code: 1. Does not conform to the interface segregation principle 2. Only has one class that inherits from it

The other thing is, there are tools included with Visual Studio that do what you aim to accomplish automatically:

  1. The Class View
  2. The Object Browser
  3. In Solution Explorer you can click on triangles to expend classes to see their functions, parameters and return types.
  4. There are three little drop down menus below the file tabs, the most right one lists all the members of the current class.

Give one the above a try before devoting time to replicating C++ header files in C#.


Furthermore, there are technical reasons not to do this... it will make your final binary larger than it needs to be. I will repeat this comment by Mark Benningfield:

Declarations in C++ header files don't end up being part of the generated binary. They are there for the compiler and linker. These C# abstract classes would be part of the generated code, to no benefit whatsoever.


Also, mentioned by Robert Harvey, technically, the closest equivalent of a header in C# would be an interface, not an abstract class.

  • 2
    You'd also end up with lots of unnecessary virtual dispatch calls (not good if your code is performance-sensitive). – Lucas Trzesniewski Jan 24 '18 at 20:13
  • 5
    here's the Interface Segregation Principle referred to. – NH. Jan 24 '18 at 20:58
  • 2
    One might also simply collapse the entire class (right click --> Outlining --> Collapse to Definitions) to get a bird's eye view of it. – jpmc26 Jan 25 '18 at 4:15
  • "what else could you have done with that time" --> Uhm, copy and paste and very really minor postwork? Takes me about 3 seconds, if at all. Of course, I could have used that time to take out a filter tip in preparation for rolling a cigarette, instead. Not really a relevant point. [Disclaimer: I love both, idiomatic C#, as well as idiomatic C++, and some more languages] – phresnel Jan 25 '18 at 12:16
  • 1
    @phresnel: I'd like to see you try and repeat a class' definitions and inherit the original class from the abstract one in 3s, confirmed with no errors, for any class that is sufficiently large to not have an easy bird's eye view over it (as that is the OP's proposed use case: supposedly uncomplicating otherwise complicated classes). Almost inherently, the complicated nature of the original class means that you can't just write the abstract class definition by heart (because that would mean you already know it by heart) And then consider the time needed to do this for an entire codebase. – Flater Jan 25 '18 at 13:56
14

First, understand that a purely abstract class is really just an interface that can't do multiple inheritance.

Write class, extract interface, is a brain dead activity. So much so that we have a refactoring for it. Which is a pity. Following this "every class gets an interface" pattern not only produces clutter it completely misses the point.

An interface should not be thought of as simply a formal restatement of whatever the class can do. An interface should be thought of as a contract imposed by the using client code detailing it's needs.

I have no trouble at all writing an interface that currently only has one class implementing it. I actually don't care if no class at all implements it yet. Because I'm thinking about what my using code needs. The interface expresses what the using code demands. Whatever comes along later can do what it likes so long as it satisfies these expectations.

Now I don't do this every time one object uses another. I do this when crossing a boundary. I do it when I don't want one object to know exactly which other object it's talking to. Which is the only way polymorphism will work. I do it when I expect the object my client code is talking to be likely to change. I certainly don't do this when what I'm using is the String class. The String class is nice and stable and I feel no need to guard against it changing on me.

When you decide to interact directly with a concrete implementation rather then through an abstraction you're predicting that the implementation is stable enough to trust not to change.

That right there is the way I temper the Dependency Inversion Principle. You shouldn't blindly fanatically apply this to everything. When you add an abstraction you're really saying you don't trust the choice of implementing class to be stable over the life of the project.

This all assumes you're trying to follow the Open Closed Principle. This principle is important only when the costs associated with making direct changes to established code are significant. One of the main reasons people disagree on how important decoupling objects is because not everyone experiences the same costs when making direct changes. If retesting, recompiling, and redistributing your entire code base is trivial to you then resolving a need for change with direct modification is likely a very attractive simplification of this problem.

There simply isn't a brain dead answer to this question. An interface or abstract class isn't something you should add to every class and you can't just count the number of implementing classes and decide it's not needed. It's about dealing with change. Which means you're anticipating the future. Don't be surprised if you get it wrong. Keep it simple as you can without backing yourself into a corner.

So please, don't write abstractions just to help us read the code. We have tools for that. Use abstractions to decouple what needs decoupling.

5

Yes that would be terrible because (1) It introduces unnecessary code (2) It will confuse the reader.

If you want to program in C# you should just get used to read C#. Code written by other people will not follow this pattern anyway.

1

Unit tests

I would strongly suggest that you write Unit tests instead of cluttering the code with interfaces or abstract classes (except where warranted for other reasons).

A well written unit test not only describes the interface of your class (like a header file, abstract class or interface would do) but it also describes, by example, the desired functionality.

Example: Where you might have written a header file myclass.h like this:

class MyClass
{
public:
  void foo();
};

Instead, in c# write a test like this:

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void MyClass_should_have_method_Foo()
    {
        //Arrange
        var myClass = new MyClass();
        //Act
        myClass.Foo();
        //Verify
        Assert.Inconclusive("TODO: Write a more detailed test");
    }
}

This very simple test conveys the same information as the header file. (We should have a class named "MyClass" with a parameterless function "Foo") While a header file is more compact, the test contains far more information.

A caveat: such a process of having a senior software engineer supply (failing) tests for other developers to solve clashes violently with methodologies like TDD, but in your case it would be a huge improvement.

  • How do you do Unit Testing (isolated tests) = Mocks needed, without interfaces? What framework supports mocking from an actual implementation, most I've seen uses an interface to swap out the implementation with the mock implementation. – Bulan Jan 26 '18 at 12:04
  • I don't think mocks nessecarily are needed for the OP:s purposes. Remember, this is not unit tests as tools for TDD, but unit test as a replacement for header files. (They might of course evolve into "usual" unit tests with mocks etc) – Guran Jan 26 '18 at 12:39
  • Ok, if I only consider OPs side of it,I understand better. Read your answer as a more general applying answer. Thx for clarifying! – Bulan Jan 27 '18 at 21:42
  • @Bulan the need for extensive mocking is often indicative of a bad design – TheCatWhisperer Jan 29 '18 at 14:23
  • @TheCatWhisperer Yes, I'm well aware of that, so no argument there, can't see that I made that statement anywhere either :D I was talking about testing in general, that all Mock-framework I've used uses Interfaces to swap out the actual implementation, and if there was some other technique on how you went about to mock if you don't have interfaces. – Bulan Jan 29 '18 at 14:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.