Skip to content

Instantly share code, notes, and snippets.

@binki
Created July 23, 2016 15:10
Show Gist options
  • Save binki/72cc0d94a3b7f2d51be0be35a4317279 to your computer and use it in GitHub Desktop.
Save binki/72cc0d94a3b7f2d51be0be35a4317279 to your computer and use it in GitHub Desktop.
Using struct as a parameter list guard
using System;
namespace ParameterDefaultsGuard
{
// The goal is to make it hard to accidentally call the wrong
// overload when wanting to provide multiple overloads and still
// allow overriding defaults through named parameters. To
// accomplish this, we create an empty struct type. This guards
// against passing in null to various positional parameters and
// that being mistakenly passed as the guard. The only way to
// instantiate such a guard is to
//
// 1. define a class member with its type as MyGuard
//
// 2. use default(MyGuard) (which we can use in our parameter
// lists).
//
// So callers will not accidentally get past the guard. This
// enables you to avoid the C# compiler complaining about multiple
// overloads matching.
class Program
{
struct MyGuard { private MyGuard(int x) { } }
static void Do(MyGuard _guard = default(MyGuard), object y = null)
=> Console.WriteLine(new { _guard, y, });
static void Do(object x, MyGuard _guard = default(MyGuard), object y = null)
=> Console.WriteLine(new { x, _guard, y, });
static void InvertedDo(object y = null)
=> Console.WriteLine(new { y, });
static void InvertedDo(object x, object y = null)
=> Console.WriteLine(new { x, y, });
static void Main(string[] args)
{
Do(); // { _guard = ParameterDefaultsGuard.Program+MyGuard, y = }
// Without guard, this would be ambiguous between the two Do() overloads.
Do(null); // { x = , _guard = ParameterDefaultsGuard.Program+MyGuard, y = }
// “error CS1503: Argument 1: cannot convert from '<null>' to 'Program.MyGuard'”
//Do(null, null);
// Providing y
Console.WriteLine();
Do(y: 3); // { _guard = ParameterDefaultsGuard.Program+MyGuard, y = 3 }
Do(null, y: 3); // { x = , _guard = ParameterDefaultsGuard.Program+MyGuard, y = 3 }
// Unwanted behavior with ambiguous overloads.
Console.WriteLine();
// Fully unambiguous.
InvertedDo(); // { y = }
// Compiler uses the one with 1 parameter even though I wanted to specify x.
InvertedDo(null); // { y = }
// Calls the one I want.
InvertedDo(y: null); // { y = }
// Calls the one I want.
InvertedDo(x: null); // { x = , y = }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment