Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Determine Whether a C# Optional Parameter was Supplied

0.00/5 (No votes)
28 Jan 2014 1  
Determine whether a C# optional parameter was supplied

Introduction

Using the following generic C# structure, a developer will be able to determine whether the optional argument value was supplied or default value has been chosen.

Background

I was designing a data access layer to call database procedures. In stored procedures, we can have default parameters and my problem was to find a way to avoid passing parameters when developer chooses to use default parameter. Imagine the following Oracle stored procedure definition:

 PROCEDURE GetEmployee (employeeId IN NUMBER,
    departmentName IN VARCHAR DEFAULT '%',
    departmentId IN NUMBER DEFAULT 100) AS ... 

My first impression was the best way to write a mapping function is:

public Employee GetEmployee(int employeeId,
    string departmentName = "%", int departmentId = 100)
{
    ...
    command.Parameters.Add("employeeId", employeeId);
    command.Parameters.Add("departmentName", departmentName);
    command.Parameters.Add("departmentId", departmentId);
    ...
} 

But this has two problems. First, you are providing something to the DbCommand which is not necessary and the second and more important problem is if a database developer changes the default value in the procedure's definition, C# is still passing the same old value which is not the default value anymore. So I thought I could solve the problem by doing something like this:

public Employee GetEmployee(int employeeId,
    string departmentName = default(string), int departmentId = default(int))
{
    ...
    command.Parameters.Add("employeeId", employeeId);
    if (departmentName != default(string))
    {
    	command.Parameters.Add("departmentName", departmentName);
    }
    if (departmentId != default(int))
    {
        command.Parameters.Add("departmentId", departmentId);
    }
    ...
} 

This was a little bit better and if we don't supply value to optional parameters everything works fine as expected. But we will not be able to pass some values to the procedure using this method. In C# default(int) equals to zero and default(string) is null. So if we need to pass null for departmentName or zero for the departmentId, method body will ignore them and procedure will be executed using departmentName = '%' and departmentId = 100 instead of departmentName = NULL and departmentId = 0.

Redefining C# method using Optional<T> generic types will give us exactly what we want:

public Employee GetEmployee(int employeeId,
    Optional<string> departmentName = default(Optional<string>),
    Optional<int> departmentId = default(Optional<int>))
{
    ...
    command.Parameters.Add("employeeId", employeeId);
    if (departmentName.HasValue)
    {
    	command.Parameters.Add("departmentName", departmentName.Value);
    }
    if (departmentId.HasValue)
    {
        command.Parameters.Add("departmentId", departmentId.Value);
    }
    ...
}

This is useful especially when we want to create proxy functions like what we do in ORM libraries.

Optional<T> Structure

public struct Optional<T>
{
    public Optional(T value)
    {
        _value = value;
        _hasValue = true;
    }

    public static explicit operator T(Optional<T> optional)
    {
        return optional._value;
    }
    
    public static implicit operator Optional<T>(T value)
    {
        return new Optional<T>(value);
    }

    T _value;
    public T Value
    {
        get { return _value; }
    }
    
    bool _hasValue;
    public bool HasValue
    {
        get { return _hasValue; }
    }

    public override string ToString()
    {
        return string.Format("Optional{ HasValue: {0}, Value: '{1}')", HasValue, Value);
    }
} 

Usage

void Main()
{
    TestFunction("required value",
        1234,
        0,
        arg05: "string value",
        arg06: string.Empty,
        arg08: ConsoleColor.Red,
        arg10: 4,
        arg11: "object value",
        arg12: ConsoleColor.Blue,
        arg13: default(Optional<object>), // this is an exception and will be considered as not supplied 
        arg14: true);
}

public void TestFunction(
    string arg01,
    Optional<int> arg02 = default(Optional<int>),
    Optional<int> arg03 = default(Optional<int>),
    Optional<int> arg04 = default(Optional<int>),
    Optional<string> arg05 = default(Optional<string>),
    Optional<string> arg06 = default(Optional<string>),
    Optional<string> arg07 = default(Optional<string>),
    Optional<ConsoleColor> arg08 = default(Optional<ConsoleColor>),
    Optional<ConsoleColor> arg09 = default(Optional<ConsoleColor>),
    Optional<object> arg10 = default(Optional<object>),
    Optional<object> arg11 = default(Optional<object>),
    Optional<object> arg12 = default(Optional<object>),
    Optional<object> arg13 = default(Optional<object>),
    Optional<object> arg14 = default(Optional<object>),
    Optional<bool> arg15 = default(Optional<bool>))
{
    Console.WriteLine("// arg01: {0}", arg01);
    Console.WriteLine("// arg02: {0}", arg02);
    Console.WriteLine("// arg03: {0}", arg03);
    Console.WriteLine("// arg04: {0}", arg04);
    Console.WriteLine("// arg05: {0}", arg05);
    Console.WriteLine("// arg06: {0}", arg06);
    Console.WriteLine("// arg07: {0}", arg07);
    Console.WriteLine("// arg08: {0}", arg08);
    Console.WriteLine("// arg09: {0}", arg09);
    Console.WriteLine("// arg10: {0}", arg10);
    Console.WriteLine("// arg11: {0}", arg11);
    Console.WriteLine("// arg12: {0}", arg12);
    Console.WriteLine("// arg13: {0}", arg13);
    Console.WriteLine("// arg14: {0}", arg14);
    Console.WriteLine("// arg15: {0}", arg15);
} 

Runtime Results

// arg01: required value
// arg02: Optional { HasValue: True, Type: [System.Int32], Value: '1234' }
// arg03: Optional { HasValue: True, Type: [System.Int32], Value: '0' }
// arg04: Optional { HasValue: False, Type: [System.Int32], Value: '0' }
// arg05: Optional { HasValue: True, Type: [System.String], Value: 'string value' }
// arg06: Optional { HasValue: True, Type: [System.String], Value: '' }
// arg07: Optional { HasValue: False, Type: [System.String], Value: '' }
// arg08: Optional { HasValue: True, Type: [System.ConsoleColor], Value: 'Red' }
// arg09: Optional { HasValue: False, Type: [System.ConsoleColor], Value: 'Black' }
// arg10: Optional { HasValue: True, Type: [System.Int32], Value: '4' }
// arg11: Optional { HasValue: True, Type: [System.String], Value: 'object value' }
// arg12: Optional { HasValue: True, Type: [System.ConsoleColor], Value: 'Blue' }
// arg13: Optional { HasValue: False, Type: [System.Object], Value: '' }
// arg14: Optional { HasValue: True, Type: [System.Boolean], Value: 'True' }
// arg15: Optional { HasValue: False, Type: [System.Boolean], Value: 'False' }

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here