C#/.NET Little Wonders – Cross Calling Constructors
- by James Michael Hare
Just a small post today, it’s the final iteration before our release and things are crazy here! This is another little tidbit that I love using, and it should be fairly common knowledge, yet I’ve noticed many times that less experienced developers tend to have redundant constructor code when they overload their constructors. The Problem – repetitive code is less maintainable Let’s say you were designing a messaging system, and so you want to create a class to represent the properties for a Receiver, so perhaps you design a ReceiverProperties class to represent this collection of properties. Perhaps, you decide to make ReceiverProperties immutable, and so you have several constructors that you can use for alternative construction: 1: // Constructs a set of receiver properties.
2: public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable, bool isBuffered)
3: {
4: ReceiverType = receiverType;
5: Source = source;
6: IsDurable = isDurable;
7: IsBuffered = isBuffered;
8: }
9:
10: // Constructs a set of receiver properties with buffering on by default.
11: public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable)
12: {
13: ReceiverType = receiverType;
14: Source = source;
15: IsDurable = isDurable;
16: IsBuffered = true;
17: }
18:
19: // Constructs a set of receiver properties with buffering on and durability off.
20: public ReceiverProperties(ReceiverType receiverType, string source)
21: {
22: ReceiverType = receiverType;
23: Source = source;
24: IsDurable = false;
25: IsBuffered = true;
26: }
Note: keep in mind this is just a simple example for illustration, and in same cases default parameters can also help clean this up, but they have issues of their own.
While strictly speaking, there is nothing wrong with this code, logically, it suffers from maintainability flaws. Consider what happens if you add a new property to the class? You have to remember to guarantee that it is set appropriately in every constructor call.
This can cause subtle bugs and becomes even uglier when the constructors do more complex logic, error handling, or there are numerous potential overloads (especially if you can’t easily see them all on one screen’s height).
The Solution – cross-calling constructors
I’d wager nearly everyone knows how to call your base class’s constructor, but you can also cross-call to one of the constructors in the same class by using the this keyword in the same way you use base to call a base constructor.
1: // Constructs a set of receiver properties.
2: public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable, bool isBuffered)
3: {
4: ReceiverType = receiverType;
5: Source = source;
6: IsDurable = isDurable;
7: IsBuffered = isBuffered;
8: }
9:
10: // Constructs a set of receiver properties with buffering on by default.
11: public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable)
12: : this(receiverType, source, isDurable, true)
13: {
14: }
15:
16: // Constructs a set of receiver properties with buffering on and durability off.
17: public ReceiverProperties(ReceiverType receiverType, string source)
18: : this(receiverType, source, false, true)
19: {
20: }
Notice, there is much less code. In addition, the code you have has no repetitive logic. You can define the main constructor that takes all arguments, and the remaining constructors with defaults simply cross-call the main constructor, passing in the defaults.
Yes, in some cases default parameters can ease some of this for you, but default parameters only work for compile-time constants (null, string and number literals). For example, if you were creating a TradingDataAdapter that relied on an implementation of ITradingDao which is the data access object to retreive records from the database, you might want two constructors: one that takes an ITradingDao reference, and a default constructor which constructs a specific ITradingDao for ease of use:
1: public TradingDataAdapter(ITradingDao dao)
2: {
3: _tradingDao = dao;
4:
5: // other constructor logic
6: }
7:
8: public TradingDataAdapter()
9: {
10: _tradingDao = new SqlTradingDao();
11:
12: // same constructor logic as above
13: }
As you can see, this isn’t something we can solve with a default parameter, but we could with cross-calling constructors:
1: public TradingDataAdapter(ITradingDao dao)
2: {
3: _tradingDao = dao;
4:
5: // other constructor logic
6: }
7:
8: public TradingDataAdapter()
9: : this(new SqlTradingDao())
10: {
11: }
So in cases like this where you have constructors with non compiler-time constant defaults, default parameters can’t help you and cross-calling constructors is one of your best options.
Summary
When you have just one constructor doing the job of initializing the class, you can consolidate all your logic and error-handling in one place, thus ensuring that your behavior will be consistent across the constructor calls.
This makes the code more maintainable and even easier to read. There will be some cases where cross-calling constructors may be sub-optimal or not possible (if, for example, the overloaded constructors take completely different types and are not just “defaulting” behaviors).
You can also use default parameters, of course, but default parameter behavior in a class hierarchy can be problematic (default values are not inherited and in fact can differ) so sometimes multiple constructors are actually preferable.
Regardless of why you may need to have multiple constructors, consider cross-calling where you can to reduce redundant logic and clean up the code.
Technorati Tags: C#,.NET,Little Wonders