In this article, I'll present a technique I came up with to allow code re-use among classes for which no logical relationship exists.
For a project I'm working on, I have three classes that all need to be able to raise an event that uses a custom
EventArgs. All three classes contain exactly the same code to do this, and when I wanted to alter the code I had to alter it in all three classes. Obviously, I would prefer to have one master copy of the code that the classes could share. I can hear you saying, "Make a base class and derive from it!", and yes, I could do that. In fact I did that as a temporary measure, but I wasn't happy with the result.
The main problem with having these classes derive from a common base class is that it defines a relationship between them that really doesn't exist. A further problem is that C# only allows single inheritance, so if any of these classes (or some future class) needs to derive from some other class, I would have to choose which to derive from. So, given that inheritance isn't really appropriate in this case, I chose to seek another way to share this code.
I will also note that using an interface will not help in this situation, because it doesn't offer implementation sharing. Inheritance offers a relationship with implementation sharing, an interface offers a relationship without implementation sharing, but I want implementation sharing without the relationship.
I'm not sure whether or not mixins would do what I want, but it doesn't matter because C# doesn't support them anyway.
What I Came Up With
I'm a big fan of partial classes[^] (most of my large classes are split among several files to segment the code into logical groups of members) so it is natural that I maintain one copy of the common code, duplicate it for each class that needs it, and have it included as one file of a partial class.
The only real obstacle to overcome is that each individual copy of the common code needs to contain the name of the class (and its namespace) in which it is to be implanted.
So, basically: copy the file and replace some text in the copy.
Sound familiar? That's what the C-preprocessor[^] does best. As a quick and dirty implementation of this technique of using partial classes to implant common code in unrelated classes, I'm using the C-preprocessor. If you want to write a utility to do it, go right ahead, I just don't feel like it right now. The particular implementation of the C-preprocessor I'm using is the one I got with WinGW; I don't remember where I got it or where it's available now so I can't include a link. I hope someone posts a message with a link. Or you may use your own favorite C-preprocessor.
Details of this Technique
- The classes that are to receive the implanted code must be marked
- Decide on place holders for the namespace and class names and write the common code file:
partial class __CLASS_NAME__
- If you are using Visual Studio, you can include this file in your project, but you may want to set the Build Action to None so it doesn't get built into the assembly.
- I chose to write a small BAT file (Implant.bat) to help with calling the C-preprocessor:
"C:\mingw\bin\cpp" -P -C -include "c:\batfiles\ImplantWarning.h"
-D__NAME_SPACE__=%3 -D__CLASS_NAME__=%4 -w "%1" "%2"
- The BAT file can then be called with:
IMPLANT sourcefile destfile namespace classname. The C-preprocessor will:
- Read the file specified in the first parameter.
- Insert the text of ImplantWarning.h (see below).
- Replace any instance of
__NAME_SPACE__ with the value of the third parameter.
- Replace any instance of
__CLASS_NAME__ with the value of the fourth parameter.
- Write the resultant text to the file specified in the second parameter.
- The BAT file includes ImplantWarning.h to prepend the following warning in the generated file:
Both these files are included in the zip file, and you may change either as you see fit.
- If you are using Visual Studio, you can put these statements in the project's pre-build event.
In my project's case, I added:
call Implant.bat ..\..\Types\MessageEventImplant.cs ..\..\dal\
dalVersionControl.MessageEventImplant.cs PIEBALD.VersionControl dalVersionControl
call Implant.bat ..\..\Types\MessageEventImplant.cs ..\..\opr\
oprVersionControl.MessageEventImplant.cs PIEBALD.VersionControl oprVersionControl
call Implant.bat ..\..\Types\MessageEventImplant.cs ..\..\api\
apiVersionControl.MessageEventImplant.cs PIEBALD.VersionControl apiVersionControl
Note that call is required when calling BAT files repeatedly.
- Once you have generated the implant files, you can refer to their contents in your code.
- If you are using Visual Studio, you can use Add existing item to add the generated files to your project.
- The debugger will be able to step through the implant file, but any changes should be made to the common file.
Because this technique uses the C-preprocessor, the common code can't contain preprocessor directives. If this is a problem, you can write your own utility.
It's a source-code-level technique, you can't implant code that's already compiled into an assembly.
In many cases, inheritance has been used simply to provide code-re-use, even when it is inappropriate. This technique provides code re-use without adding unnecessary inheritance. It can eliminate some of the mis-use of inheritance (and multiple inheritance in languages that allow it) by providing another form of aggregation.
- 2009-12-05 First submitted