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>), 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