We have a struct called Vector3D
and for some methods we want the (default) zero vector as default argument but for others we want a different, non-default vector (say, a unit vector in x ditrection) as the default argument.
public struct Vector3D
{
public double X;
public double Y;
public double Z;
public Vector3D(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public static Vector3D UnitX()
{
return new Vector3D(1, 0, 0)
}
}
Now, a method with a zero vector as default argument can be easily achieved like this:
public void MyVectorMethod(Vector3D vec = new Vector3D())
{ ... }
However, if we want a different default argument (say, a unit vector in x-direction), the following does not work, because default arguments must be compile time constants:
public void MyVectorMethod(Vector3D vec = Vector3D.UnitX())
{ ... }
One way that we came up with was to use a nullable Vector3D
, like:
public void MyVectorMethod(Vector3D? vec = null)
{
Vector3D actualVec;
if (vec == null) actualVec = Vector3D.UnitX();
else actualVec = (Vector3D) vec;
...
}
This is equivalent to a unit vector in x-direction as the default argument. But the code is not very elegant and we have to add an extra type cast.
Is there a better (i.e., more elegant and without cast) way to achieve the same?
Edit: As Matthew suggested, a neat way would be to use a method overload with empty argument. However, in our project that approach is not always straightforward to apply, because the methods typically have multiple default arguments and we couple the library to a Python scripting interface with IronPython (which sometimes leads to issues with type guessing).
Consider for example a method like this ...
public void MyVectorMethod(Vector3D vec1 = new Vector3D(),
Vector3D vec2 = new Vector3D(), Vector3D vec3 = new Vector3D())
{ ... }
A nullable Vector3D
is the approach that I would also follow. So the question now is how to make your code snippet more elegant.
Here is your code snippet:
Vector3D actualVec;
if (vec == null) actualVec = Vector3D.UnitX();
else actualVec = (Vector3D) vec;
First of all, it can be made more elegant by dropping the type cast:
Vector3D actualVec;
if (vec == null) actualVec = Vector3D.UnitX();
else actualVec = vec.Value;
Then, it can be more elegantly expressed by using a conditional instead of an if-statement:
Vector3D actualVec = vec == null? Vector3D.UnitX() : vec.Value;
Then, it can be even more elegantly expressed by using idiomatic C#:
Vector3D actualVec = vec ?? Vector3D.UnitX();
And finally, it can be made yet even more elegant by dropping the unnecessary local variable:
vec ??= Vector3D.UnitX();
By doing this you would be re-assigning a value to a parameter of a function, which scares the bejesus out of some people, but fear not, there is absolutely nothing wrong with re-assigning parameters, it is actually a very good practice, because it reduces the number of identifiers in scope and it prevents the original (unwanted) value from being accessed by mistake. The practice of refraining from re-assigning values to parameters is cargo cult programming. (Wikipedia)
Also note that once you have reassigned your parameter this way, you can keep accessing vec.Value
without receiving warning CS8629: Nullable value type may be null
because the compiler knows that it cannot be null.