16

How do I check if an optional argument was passed to a method?

public void ExampleMethod(int required, string optionalstr = "default string",
    int optionalint = 10)
{

    if (optionalint was passed)
       return;
}

Another approach is to use Nullable<T>.HasValue (MSDN definitions, MSDN examples):

int default_optionalint = 0;

public void ExampleMethod(int required, int? optionalint,
                            string optionalstr = "default string")
{
    int _optionalint = optionalint ?? default_optionalint;
}
Yariv
  • 12,945
  • 19
  • 54
  • 75
  • What I ususally do is give it a default value (fi -1 in your case). When it has a different value than -1 it has been passed. You could also make the int nullable (int?) and give it a default of null – Paul Sinnema Jan 02 '14 at 16:07
  • 6
    Why would you want to check? The point of a default is that you don't need to know – Paul Michaels Jan 02 '14 at 16:07
  • @pm_2, from MSDN:"For an example of when you might use a nullable type, consider how an ordinary Boolean variable can have two values: true and false. There is no value that signifies "undefined"." – Yariv Jan 02 '14 at 17:54
  • 2
    Nullable types and optional parameters are mot the same thing. – Paul Michaels Jan 02 '14 at 18:24
  • If you just want to avoid duplicate constants in comparison (if ("default string" == "default string") and not really check whether parameter was passed, you could use reflection to get default and then compare to that. Probably a bit over the top, but then you should be able to change default parameter in signature without having to change code. http://stackoverflow.com/a/29156478/586754 – Andreas Reiff Oct 08 '15 at 08:23

10 Answers10

19

Well, arguments are always passed. Default parameter values just ensure that the user doesn't have to explicitly specify them when calling the function.

When the compiler sees a call like this:

ExampleMethod(1);

It silently converts it to:

ExampleMethod(1, "default string", 10);

So it's not techically possible to determine if the argument was passed at run-time. The closest you could get is:

if (optionalstr == "default string")
   return;

But this would behave identically if the user called it explicitly like this:

ExampleMethod(1, "default string");

The alternative, if you really want to have different behavior depending on whether or not a parameter is provided, is to get rid of the default parameters and use overloads instead, like this:

public void ExampleMethod(int required)
{
    // optionalstr and optionalint not provided
}

public void ExampleMethod(int required, string optionalstr)
{
    // optionalint not provided
}

public void ExampleMethod(int required, string optionalstr, int optionalint)
{
    // all parameters provided
}
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • Is it a good solution to use a nullable-type argument, as in my question's edit? – Yariv Jan 02 '14 at 18:07
  • 1
    @Yariv That doesn't actually solve the problem, it just produces another level of indirection. The user may still call it with an explicit parameter, `ExampleMethod(10, null)`, which is indistinguishable from `ExampleMethod(10)` at run time. Although that might be sufficient for your purposes. – p.s.w.g Jan 02 '14 at 18:19
8

You can't, basically. The IL generated for these calls is exactly the same:

ExampleMethod(10);
ExampleMethod(10, "default string");
ExampleMethod(10, "default string", 10);

The defaulting is performed at the call site, by the compiler.

If you really want both of those calls to be valid but distinguishable, you can just use overloading:

// optionalint removed for simplicity - you'd need four overloads rather than two
public void ExampleMethod(int required)
{
    ExampleMethodImpl(required, "default string", false);
}

public void ExampleMethod(int required, string optionalstr)
{
    ExampleMethodImpl(required, optionalstr, true);
}

private void ExampleMethodImpl(int required, int optionalstr, bool optionalPassed)
{
    // Now optionalPassed will be true when it's been passed by the caller,
    // and false when we went via the "int-only" overload
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
4

You can't check that, because method with optional parameters is a regular method with all parameters, including those which have default values. So, your method will be compiled into:

public void ExampleMethod(int required, string optionalstr, int optionalint)
{

}

Default values are inserted by compiler in the call point. If you'll write

ExampleMethod(42);

Then compiler will generate call

ExampleMethod(42, "default string", 10);

You can compare if optionalstr or optionalint has value equal to default value, but you can't really say if it was provided by compiler or by developer.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • Is it a good solution to use a nullable-type argument, as in my question's edit? – Yariv Jan 02 '14 at 18:10
  • @Yariv with nullable you still will not be able to say whether value was not provided, or `default_optionalint` have been passed to your method manually. Default value is just one of possible values parameter can have. – Sergey Berezovskiy Jan 02 '14 at 18:37
3

You can't do this in C#.

However, you could overload the function to take differing arguments. That, by the way, is the only approach you can take in Java so you'd be in good company if you adopt it.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 2
    I wanted to upvote this answer, but then I saw "Java" and I won't. – Sinatr Nov 13 '20 at 08:24
  • @Sinatr: Reprehensible indeed: if I were you I'd consider downvoting this. I might even supply you with the reputation by upvoting one of your answers. (Seriously I thought Java had its shortcomings cf. a well-bred language like C++ until I came across C#.) – Bathsheba Nov 13 '20 at 08:44
3

You can't, so you need to find a different way to check for the "optional" parameter. You can pass in a null if the parameter isn't being used, and then check

if (optionalstr != null)
{
    // do something
}

You can also overload the method, having one taking the optional parameters and one that doesn't take the optional parameters. Also, you can make it so that the method without the optional parameters passes in nulls to one of the overloaded methods.

public void ExampleMethod(int required)
{
    ExampleMethod(required, null, 0);
}

public void ExampleMethod(int required, string optionalstr = "default string",
int optionalint = 10)
{

}
ScottK
  • 277
  • 2
  • 5
  • 16
1

you can not check directly but you can check it by default value. for example:

public void ExampleMethod(int required, string optionalstr = "default string",
    int optionalint = 10)
{

    if (optionalint == 10)
       return;
}

or

public void ExampleMethod(int required, string optionalstr = "default string",
    int? optionalint)
{

    if (required.HasValue==false)
       return;
}

Approach 2:

Also you can use override methods:

public void ExampleMethod(int required, string optionalstr = "default string")
{
     //When this method called, means optionalint was NOT passed
}

public void ExampleMethod(int required, string optionalstr = "default string",
    int optionalint)
{
    //When this method called, means optionalint was passed
}
Hadi Sharifi
  • 1,497
  • 5
  • 18
  • 28
0

Another approach is to use Nullable<T>.HasValue (MSDN definitions, MSDN examples):

int default_optionalint = 0;

public void ExampleMethod(int required, int? optionalint,
                            string optionalstr = "default string")
{
    int _optionalint = optionalint ?? default_optionalint;
}
Yariv
  • 12,945
  • 19
  • 54
  • 75
0

Necromancing an old thread, but thought I'd add something.

You can use default(int) or new int()

For certain cases the default values can be used to check where an argument was passed. This works for multiple datatypes. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/default-values-table

public void ExampleMethod(int optionalInt = default(int))
{
    if (optionalInt == default(int)) //no parameter passed
       //do something
    else  //parameter was passed
       return;
}

This works particularly well when passing database ids where records hold unsigned integers greater than 0. So you know 0 will always be null and no ids can be negative. In other words, everything other than default(datatype) will be accepted as a passed parameter.


I would personally go with overloading methods, but another approach (which is perhaps outside the scope of this question) is to make the input parameters into a model. Then use the model force boundaries (like 10 being default parameter), which lets the model deal with the parameter, instead of the method.

public class Example
{
    //...other properties

    private int _optionalInt;
    public int OptionalInt {
        get => _optionalInt;
        set {
            if (value <= default(int))
                throw new ArgumentOutOfRangeException("Value cannot be 0 or less");
            else if (value == 10)
                throw new ArgumentOutOfRangeException("Value cannot be 10");
            else
                _initValue = value;
        } 
    }

    public Example(int optionalInt)
    {
        OptionalInt = optionalInt; //validation check in constructor
    }
}

This forces behavior before it reaches your method.

Hope this helps someone.

Nick De Beer
  • 5,232
  • 6
  • 35
  • 50
  • 1
    Note that using `default(int)` or `new int()` is exactly the same as using `0`... and it doesn't prevent the caller from passing in 0 explicitly. – Jon Skeet Aug 19 '18 at 14:03
  • Correct, however it does improve readability as 0 could mean multiple things, where default values most definitely means what is written, an unassigned default value. Passing 0 in this case, would still mean the intended though. Thanks for the clarity! – Nick De Beer Aug 19 '18 at 14:49
  • "default values most definitely means what is written, an unassigned default value" - no, they don't. They mean "the default value of that type" - so 0 in this case. Passing in 0 would make your code claim that it hadn't been passed in by the calling code, even when it had. You *cannot* detect whether the calling code has passed an argument explicitly or not. – Jon Skeet Aug 19 '18 at 14:57
  • Hence the bold **certain cases** in my answer. Default and 0 are handled as the same in the first line of the method. The code wont prevent them passing 0 or even default(int) as a parameter (like overloading), but by placing meaning that `parameter = default(int) = new int() = 0` is uninitialized the method knows that the parameter is irrelevant, in this case skipping to return; Its not an end all solution and definitely has its holes, but a different way of doing it, which I'd thought it document here. Also I think `== default(long)` is more readable than `== 0` haha. – Nick De Beer Aug 19 '18 at 16:23
  • I suggest you edit to be a lot clearer what you mean by "certain cases". I interpreted that to be "certain situations, such as when you're using a value type and don't expect 0 to be passed explicitly". If you meant "You can detect whether the value passed in was the same as the default value, but you can't tell whether that was done explicitly or implicitly" then that's a very different matter. In terms of terminology, the parameter is *never* uninitialized - I'm not sure what you're intending that to mean. – Jon Skeet Aug 19 '18 at 16:30
  • I'm not sure what are you not understanding. By using default values and checking for default values in the method its implied that any value other than 0 or default means the parameter was passed, regardless of passing 0 (because in the context of the method - it bears no meaning, hence default). I explained it in my example with "unsigned integers" part as well. Sorry typo, I meant default. You are welcome to edit the answer to improve it. Thanks! – Nick De Beer Aug 19 '18 at 18:37
  • I'm afraid I don't understand what you were trying to say well enough to edit the answer. But fundamentally, the answer is "No, you can't tell whether the argument was passed explicitly or not." The use of the `default` operator is orthogonal to "If you've got a value which you *believe* will never be passed explicitly, you can use that as the default parameter value". That's a valid technique, but I think you've made it more confusing by introducing the default operator into things. -1 is just as likely to be a "never passed deliberately" value (probably *more* likely than 0 actually). – Jon Skeet Aug 19 '18 at 20:41
  • I think that might be where the confusion comes in, surely you could use -1 or whichever value you prefer, but there isn't a c# predefined identifier for that value except the default value expression. Some of the other answes already covered those points so I just wanted to add something thats alternative to theirs. Thanks for scratching my brain, very insightful conversation. I'll see if I can word it a bit better. – Nick De Beer Aug 19 '18 at 21:13
0

Use nullable is a good solution.. see example below

public static void ExampleMethod(bool? optionalBool = null)
{
    Console.WriteLine(optionalBool == null ? "Value not passed" : "Value passed");
}


public static void Main()
{
    ExampleMethod();
    ExampleMethod(true);
    ExampleMethod(false);
}
alhpe
  • 1,424
  • 18
  • 24
-2

To determine whether an argument has been passed to an optional parameter

  1. Define an extremely unlikely value as the default for the parameter.
  2. If the optional parameter is a reference type such as a String, you can use null as the default value, provided this is not an expected value for the argument.
  3. In the procedure code, compare the parameter against the default value and take the appropriate action.

https://msdn.microsoft.com/en-us/library/849zff9h(v=vs.100).aspx

OrganicCoder
  • 107
  • 1
  • 4
  • That doesn't allow you to determine if the caller provided a value, because the caller can always choose to explicitly provide whatever default value you use, as was stated in several other answers. – Servy Jan 05 '17 at 21:33
  • @Servy...you did not read the answer properly..can you go through one more time. Besides, its not my answer, it actually taken from Microsoft documentation, see the link. – OrganicCoder Jan 05 '17 at 21:40
  • Apparently you didn't actually read the question properly. The question asks how to distinguish between a caller not providing a value for a parameter with a default value and the caller providing the default value. Your answer doesn't provide a solution to that. The link specifically states that it's not a solution to that, so it doesn't really help your case. – Servy Jan 05 '17 at 22:01
  • There is no right answer to this question and never will be one. That's the reason Microsoft documentation stresses that its not a solution. For your information, this feature was introduced way long back as far as visual Basic 5.0. – OrganicCoder Jan 09 '17 at 15:15
  • Of course there's a right answer. The right answer is that it's impossible, as other answers have stated, and as the documentation you've linked to states. Your assertion that it is in fact possible is a wrong answer. I fail to see how the time the feature was added has anything to do with this. Also, the question is about C#, not VB. – Servy Jan 09 '17 at 15:22