Undefinable 0.1.0

dotnet add package Undefinable --version 0.1.0
NuGet\Install-Package Undefinable -Version 0.1.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Undefinable" Version="0.1.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Undefinable --version 0.1.0
#r "nuget: Undefinable, 0.1.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Undefinable as a Cake Addin
#addin nuget:?package=Undefinable&version=0.1.0

// Install Undefinable as a Cake Tool
#tool nuget:?package=Undefinable&version=0.1.0

Undefinable

Represent a value as undefined instead of reserving a specific assigned value as "this is undefined".

Where do I get it

Undefinable can be installed using the Nuget package manager

PM> Install-Package Undefinable

or the dotnet CLI.

dotnet add package Undefinable

Why?

We use the term "undefinable" because the value might be defined, similar to how a Nullable<T> might have a non-null value. But unlike nullable, T is not restricted to "non-nullable value types", and allows for null as a defined value.

How do I use it

Some syntaxes may or may not be supported by your language version

Define an Undefinable<T>

  • As a variable with an undefined value

    Undefinable<string> myString;
    myString = default;
    myString = new Undefinable<string>();
    myString = default(Undefinable<string>);
    myString = Undefinable<string>.Undefined;
    myString = Undefined.String; // available for some well-known types
    
  • As a variable with an assigned value

    Undefinable<string> myString;
    myString = "my string";
    myString = null;
    
  • As a method parameter

    void DoStuff(Undefinable<int> myBool) 
    {
    }
    
    // undefined
    DoStuff(default);
    DoStuff(new Undefinable<int>());
    DoStuff(new());
    DoStuff(Undefinable<int>.Undefined);
    DoStuff(Undefined.Int);
    
    // defined
    DoStuff(123);
    
  • As an optional method parameter (various syntaxes)

    If not specified by the caller, then defaults to undefined

    void DoStuff(Undefinable<int> myInt = default)
    {
    }
    
    void DoStuff(Undefinable<int> myInt = new())
    {
    }
    
    void DoStuff(Undefinable<int> myInt = new Undefinable<int>())
    {
    }
    

IsDefined Property

  • returns true when a value is assigned

    Undefinable<string> myString = "my string";
    var isDefined = myString.IsDefined; // true
    
  • returns false when no value is assigned

    Undefinable<string> myString = default;
    var isDefined = myString.IsDefined; // false
    

Value Property / Implicit Conversion

  • When IsDefined = true, the assigned value will be returned.

    Undefinable<string> myString = "my string";
    var value = myString.Value; // "my string"
    string implicitValue = myString; // "my string"
    
  • When IsDefined = false, an InvalidOperationException will be thrown.

    Undefinable<string> myString = default;
    var value = myString.Value; // throws an InvalidOperationException 
    string implicitValue = myString; // throws an InvalidOperationException
    

GetValueOrDefault Method

Retrieves the Value of the current Undefinable<T> object, or the default value.

  • When IsDefined = true, the assigned value will be returned.

    Undefinable<string> myString = "my string";
    myString.GetValueOrDefault(); // "my string"
    myString.GetValueOrDefault("my default"); // "my string"
    myString.GetValueOrDefault(() => "my default"); // "my string"
    await myString.GetValueOrDefaultAsync(async () => Task.FromResult("my default")); // "my string"
    
  • When IsDefined = false, the (specified) default value will be returned.

    Undefinable<string> myString = default;
    myString.GetValueOrDefault(); // null
    myString.GetValueOrDefault("my default"); // "my default"
    myString.GetValueOrDefault(() => "my default"); // "my default"
    await myString.GetValueOrDefaultAsync(async () => Task.FromResult("my default")); // "my default"
    

ToString Method

  • When IsDefined = true, the assigned value's ToString() result will be returned. If null, then null will be returned.

    Undefinable<string> myString = "my string";
    myString.ToString(); // "my string"
    
  • When IsDefined = false, the ToString() method will return "<undefined>".

    Undefinable<string> myString = default;
    myString.ToString(); // "<undefined>"
    

Practical Example

This example tests a SetPassword api method. Because only the test data varies, each test calls a helper method with the necessary parameters. Undefined parameters are populated with a sensible value.

public class SetPasswordTests
{
    [Fact]
    public void Password_IsNull() => Test(password: null, expectedError: "password is required");

    [Fact]
    public void Password_IsEmpty() => Test(password: string.Empty, expectedError: "password is invalid");

    [Fact]
    public void Password_IsTooShort() => Test(password: "Sh0rt!", expectedError: "password is too short");

    [Fact]
    public void Password_IsTooWeak() => Test(password: "weakpassword", expectedError: "password is too weak");

    [Fact]
    public void Password_IsStrong() => Test(password: RandomStrongPassword);

    [Fact]
    public void UserId_IsNull() => Test(userId: null, expectedError: "userId is required");

    [Fact]
    public void UserId_IsEmpty() => Test(userId: string.Empty, expectedError: "userId is invalid");

    [Fact]
    public void UserId_DoesNotExist() => Test(userId: "does not exist", expectedError: "userId not found");

    private IApi _api = new Api();
    private string RandomUsername => "user" + DateTime.Now.Ticks;
    private string RandomStrongPassword => Path.GetRandomFileName() + "A1a";

    private void Test(
        Undefinable<string> userId = default, 
        Undefinable<string> password = default,
        Undefinable<string> expectedError = default)
    {
        /* ARRANGE */
        // if userId is not defined, create a new user
        userId = userId.GetValueOrDefault(() => _api.CreateUser(RandomUsername, RandomStrongPassword).UserId);

        // if password is not defined, define a strong one
        password = password.GetValueOrDefault(RandomStrongPassword);

        /* ACT */
        var setPasswordResult = _api.SetPassword(userId, password);

        /* ASSERT */
        if (expectedError.IsDefined)
        {
            // check for error
            Assert.False(setPasswordResult.Success);
            Assert.Equal(expectedError, setPasswordResult.Error);
        }
        else
        {
            // check for success
            Assert.True(setPasswordResult.Success);
        }
    }
}

Serialization

There currently isn't a great story for serialization without adding dependencies on third-party nuget packages. For now, avoid using an Undefinable<T> in situations where it might be serialized.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.1.0 148 12/1/2023
0.0.0 103 11/30/2023