0

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())
{ ... }
7
  • 2
    Why not just provide a method overload that calls the other method with the value that you want? public void MyVectorMethod() => MyVectorMethod(Vector3D.UnitX()); Commented Feb 1, 2024 at 9:19
  • @MatthewWatson That would probably be the best way, thanks for pointing it out. Unfortunately we cannot use it in our case, I'll edit the question to explain.
    – Amos Egel
    Commented Feb 1, 2024 at 9:33
  • 2
    When the context prevents typical solutions you should Tag accordingly. Like mentioning Python/IronPython.
    – Ralf
    Commented Feb 1, 2024 at 9:46
  • Even after the edit I don't see why you can't simply provide multiple method overloads with different numbers of parameters, all of which call the method with all the parameters, Commented Feb 1, 2024 at 9:51
  • Although I would generally avoid such overloads and instead introduce a number of well-named methods to call the appropriate method with the correct parameters. Commented Feb 1, 2024 at 9:53

1 Answer 1

1

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.