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?

  • 14
    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
  • 18
    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
  • @Bent Why is that a bad habit? 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. – Dani Barca Casafont Jan 24 '18 at 15:15
  • 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
  • 1
    Use interfaces if you need it really bad. – Machado Jan 24 '18 at 15:30
  • 17
    The equivalent construct in C# is an interface, not an abstract class. – Robert Harvey Jan 24 '18 at 15:55
  • 4
    [Have you looked at the C# `partial` keyword?](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods) It can be used to split up class definition and members from the implementation, spread across an arbitrary number of files. Visual Studio uses it heavily in conjunction with WinForms to separate your code from tool-generated code, but it can also be used for this. – Cody Jan 24 '18 at 16:59
  • 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. – Vanity Slug - codidact.com Jan 24 '18 at 18:58
  • 3
    If you want a list of class methods and properties, you can easily generate it using Doxygen or a similar tool. No need to write it manually. – dan04 Jan 24 '18 at 23:25
  • "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?" Yes; it's not idiomatic code and it would be unprofessional to impose your C++ preferences onto your fellow C# developers. – pmf Jan 25 '18 at 08:48
  • Surely interfaces are more appropriate here…? – Martin Bean Jan 25 '18 at 10:09
  • Note that even c++ has deprecated headers because they are just an all-round horrible programming model, a workaround from the 70s forced upon generation after generation of programmer. – Voo Jan 25 '18 at 11:36
  • 1
    @Bent: I think the OP is fully aware of this. Personally, having used C++ for decades, I always enjoyed header files as a kind of documentation and contract based programming. – phresnel Jan 25 '18 at 11:56
  • 2
    @Robert: I'd say neither interface, nor abstract classes are equivalent to header files. While both provide some kind of contract, a header file is way more general, plus a declaration in a header file may be an interface declaration, but does not have to. I wouldn't put them into the same pot. – phresnel Jan 25 '18 at 11:58
  • 1
    @Voo: Headers have been deprecated? When? Where? – phresnel Jan 25 '18 at 12:01
  • 1
    @Cody: `partial` methods and classes are indeed what is _most_ equivalent to class declarations. But it's ugly :D – phresnel Jan 25 '18 at 12:06
  • @phresnel Marginal hyperbole, but modules will delegate headers to legacy code sooner than later. – Voo Jan 25 '18 at 12:12
  • @Voo I wouldn't be so sure about that... For newly written code, yeah, why not. But header files will be still with us, if only to maintain backwards compatibility. C++ can't afford to throw away decades worth of code. – Mael Feb 02 '18 at 09:53
  • @Mael That's exactly what I understand under the meaning "deprecated": Avoid for new code because it has been superseded with a better alternative, but still available for legacy code. – Voo Feb 02 '18 at 11:41

4 Answers4

45

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 cpp 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 an 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 expand classes to see their functions, parameters and return types.
  4. There are three little drop down menus below the file tabs, the rightmost one lists all the members of the current class.

Give one of 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.

Pang
  • 313
  • 4
  • 7
TheCatWhisperer
  • 5,231
  • 1
  • 22
  • 41
  • 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](http://www.oodesign.com/interface-segregation-principle.html) 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 04: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
  • The closest equivalent would be `partial` methods and classes, which separate declaration from definition. Header files and interfaces are part of orthogonal concepts. – phresnel Jan 25 '18 at 12:18
  • 2
    @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
  • @Flater I think he means 3 seconds for each method/attribute, similarly to what it takes to do the same in C++. – Dani Barca Casafont Jan 25 '18 at 16:51
  • Don't forget about time to find both signatures when you need to rename something for clarity..., – TheCatWhisperer Jan 25 '18 at 17:49
  • You must break an awful lot of mocked unit tests while following these criteria. You do have tests, right? – Mr Cochese Jan 26 '18 at 10:28
  • @MrCochese I do have tests... *now*. People whom write useless interfaces, also seem to neglect unit tests, so it was not a concern. – TheCatWhisperer Jan 26 '18 at 15:02
  • @Flater: As Dani writes. Please also note that my style does typically not involve large class hierarchies and in general, not many class members. I separate concerns, data and algorithms. My typical class is very, very small, and more often than not does not derive anything at all. Interfaces are a different story, but then, interfaces as a Tool are not inherenently bound to what constitutes OOP. – phresnel Jan 28 '18 at 19:46
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 its 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 than 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.

Pang
  • 313
  • 4
  • 7
candied_orange
  • 102,279
  • 24
  • 197
  • 315
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.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
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.

Guran
  • 545
  • 2
  • 9
  • 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