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?

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
  • 335
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
  • 335
candied_orange
  • 119,268
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
  • 61,955
  • 21
  • 135
  • 189
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