C# Coding Solutions—Avoiding Parameters That Have No Identity

Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio


Jump to: navigation, search
CSharp-Online.NET:Articles
C# Articles

C# Coding Solutions

© 2006 Christian Gross

Avoiding Parameters That Have No Identity

Writing methods means using parameters, and that means the potential to make the mistake of using parameters that have no identity. Of course you shouldn’t avoid writing methods, but be careful.

Let’s start with a simple problem and provide a well-defined solution. The DateTime class is an example of a badly designed constructor that can be easily confusing as to what is a day, month, or year:

public DateTime(int year, int month, int day) {}

The compiler sees the declaration as

public DateTime( int, int, int);

If I wrote the following code for December 11, 2002, assuming an American date format, the compiler could not tell me what I did wrong:

DateTime date = new DateTime(12, 11, 2);
Console.WriteLine( date.ToString());

To make matters worse, the runtime does not even mark this as an error. Running the code yields the following result:

11/2/0012 12:00:00 AM

The generated format is Canadian formatting and the computer thinks the date is February 11 of the year 12 AD. I told you that the generated format is a Canadian date, but can you know that from the formatting? For example, the date could have been a US-formatted November 2. To know the answer, we would have to look at the day, month, and year members of the class.

The simplest solution to parameters with no identity is to do nothing and hope for the best. With IDEs like Visual Studio, writing good .NET documentation will generate the parameter definitions using IntelliSense. It is not always possible to convert the parameters into having an identity. If you do nothing, then you should convert the .NET documentation to generate the appropriate information that the IDEs will pick up.

A technical solution is to convert the DateTime types into enumerations, as follows:

enum DayEnumeration : int { }
enum MonthEnumeration : int {
    January = 1,
    February = 2,
    March = 3,
    April = 4,
    May = 5,
    June = 6,
    July = 7,
    August = 8,
    September = 9,
    October = 10,
    November = 11,
    December = 12
}
enum YearEnumeration : int { }
class FixedDateTime {
    public FixedDateTime(
        YearEnumeration year,
        MonthEnumeration month,
        DayEnumeration day) {
    }
}

The source code contains three enumerations: DayEnumeration, MonthEnumeration, and YearEnumeration. DayEnumeration and YearEnumerations have no values, and that is OK because none are needed. The enumeration definition MonthEnumeration could do without defined values as well, but I’ve defined some for convenience. The constructor for FixedDateTime uses the three enumerations in the exact same calling sequence as DateTime. The difference is that the caller of FixedDateTime would know which field is which. The following code illustrates how the developer could have easily caught the error using the enumerations:

FixedMyDateTime date = new FixedMyDateTime(
    (YearEnumeration)12,
    (MonthEnumeration)11,
    (DayEnumeration)2);

As the code is written the int values are typecast from the int value to the enumeration, which can then be typecast back to the int type.

You may think the typecasts are neither legal nor helpful, but think again. It is perfectly legal to declare an enumeration and then use int typecasts. When you use the casts you will notice right away which field is the day, month, and year, and in the example there is a problem.

Using the enumeration to int and back typecast works only for int types. In the declaration of the enumeration, the base type of the enumeration was int, and that made the type compatible with the class DateTime.

Now let’s look at a more complicated problem and a more complicated solution. The following source code is an example of a confusing constructor declaration using same types:

string message = "message";
string param = "param";
 
ArgumentException exception1 =
    new ArgumentException( message, param);
ArgumentNullException expection2 =
    new ArgumentNullException( param, message);

In the example there are two .NET classes: ArgumentException and ArgumentNullException. Both classes have a constructor with the same identifiers. The difference between the two constructors is the order of the parameters. This is confusing and a bad programming practice because there is no consistency in the parameter ordering.

What makes this an especially bad coding error is that the development environment cannot help you because the compiler sees the following constructor declarations:

ArgumentException( string, string);
ArgumentNullException( string, string);

The compiler sees two declarations that each use two string parameters. Fixing the ArgumentException and ArgumentNullException is difficult because the classes expect to see string arguments. Using .NET it is impossible to convert the string types into another type. If you have two buffers of arbitrary content, it is not easy to indicate to the compiler what the buffer’s contents mean.

There is a solution and that involves wrapping a disposable type on top of the type that you want to improve. The following is an example implementation of fixing the ArgumentException using a wrapped disposable type that acts as a factory:

class FixedArgumentException : ArgumentException {
    string _message;
    string _paramName;
 
    public FixedArgumentException() {}
 
    public FixedArgumentException
        MessageConstructorParameter(string message) {
        _message = message;
        return this;
    }
    public FixedArgumentException
       ParamConstructorParameter(string param) {
        _message = param;
        return this;
    }
    public override string Message {
        get { return _message; }
    }
 
    public override string ParamName {
        get { return _paramName; }
    }
    public ArgumentException GenerateBaseType() {
        return new ArgumentException( _message, _paramName);
    }
}

In the solution FixedArgumentException subclasses ArgumentException and implements the properties Message and ParamName. The idea is to make FixedArgumentException look and feel like an ArgumentException type. The FixedArgumentException constructor has no parameters because the parameter values will be defined using the methods MessageConstructorParameter and ParamConstructorParameter. Each of the methods returns a reference allowing the methods to be called using a chained mechanism. An example of using the explained methods follows:

throw new FixedArgumentException()
    .MessageConstructorParameter( "example")
    .ParamConstructorParameter( "no parameter");

In the code the throw and new keywords are used as usual when throwing an exception. But referencing of the methods MessageConstructorParameter and ParamConstructorParameter after the type instantiation is a new approach. The method calls are being chained together because the methods keep returning the current object instance. Using the methods results in a clear definition of the object’s state.

For situations in which the object cannot be subclassed or made to look and feel like the object, the solution is to add a GenerateBaseType method. This would work for the DateTime definition because it is a sealed type definition. In the case of the FixedArgumentException, the client code would be modified slightly to the following:

throw new FixedArgumentException()
    .MessageConstructorParameter( "example")
    .ParamConstructorParameter( "no parameter")
    .GenerateBaseType();

In the modified code the last method call is GenerateBaseType, and it creates the type. The state of the created type depends on the chained methods that are called before calling GenerateBaseType.

When writing methods and constructors with parameters, remember the following:

  • Having a parameter signature with multiple identical types can be a problem. Be cautious.
  • The simplest solution to multiple identical parameters is to write .NET documentation for the API that will be exposed, allowing the development environment to pick up the information for IntelliSense.
  • For int base types, you can use enumerations to distinguish parameters.
  • For all other types, you can use disposal techniques to define constructor parameters.
  • Using disposal techniques when defining constructor parameters can reduce the number of permutations and combinations for defining the state of a type. This is especially useful when the constructors are used for immutable objects.
  • The disposal-type techniques work well for reference types, but are less efficient for value types.


Previous_Page_.gif Next_Page_.gif


Personal tools