{"id":25654242,"url":"https://github.com/bert2/nullable.extensions","last_synced_at":"2025-04-16T14:53:01.796Z","repository":{"id":40376815,"uuid":"238925633","full_name":"bert2/Nullable.Extensions","owner":"bert2","description":"A set of C# extension methods to help working with nullable types by implementing the Maybe monad on top of `T?`.","archived":false,"fork":false,"pushed_at":"2023-02-22T01:38:42.000Z","size":90,"stargazers_count":16,"open_issues_count":5,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-10T09:55:42.877Z","etag":null,"topics":["extension-methods","functional-programming","functor","isomorphism","maybe-monad","monad","nullable-reference-types","nullable-types"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bert2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-07T13:10:21.000Z","updated_at":"2024-09-20T08:14:56.000Z","dependencies_parsed_at":"2023-01-29T20:15:52.749Z","dependency_job_id":null,"html_url":"https://github.com/bert2/Nullable.Extensions","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2FNullable.Extensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2FNullable.Extensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2FNullable.Extensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2FNullable.Extensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bert2","download_url":"https://codeload.github.com/bert2/Nullable.Extensions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249252431,"owners_count":21238182,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["extension-methods","functional-programming","functor","isomorphism","maybe-monad","monad","nullable-reference-types","nullable-types"],"created_at":"2025-02-23T20:18:57.664Z","updated_at":"2025-04-16T14:53:01.777Z","avatar_url":"https://github.com/bert2.png","language":"C#","readme":"# Nullable.Extensions\n\n[![build](https://img.shields.io/appveyor/build/bert2/nullable-extensions/master?logo=appveyor)](https://ci.appveyor.com/project/bert2/nullable-extensions/branch/master) [![tests](https://img.shields.io/appveyor/tests/bert2/nullable-extensions/master?compact_message\u0026logo=appveyor)](https://ci.appveyor.com/project/bert2/nullable-extensions/branch/master) [![coverage](https://img.shields.io/codecov/c/github/bert2/Nullable.Extensions/master?logo=codecov)](https://codecov.io/gh/bert2/Nullable.Extensions) [![CodeFactor](https://www.codefactor.io/repository/github/bert2/nullable.extensions/badge)](https://www.codefactor.io/repository/github/bert2/nullable.extensions) ![last commit](https://img.shields.io/github/last-commit/bert2/Nullable.Extensions/master?logo=github) [![nuget package](https://img.shields.io/nuget/v/Nullable.Extensions.svg?logo=nuget)](https://www.nuget.org/packages/Nullable.Extensions) [![nuget downloads](https://img.shields.io/nuget/dt/Nullable.Extensions?color=blue\u0026logo=nuget)](https://www.nuget.org/packages/Nullable.Extensions)\n\n`Nullable.Extensions` is a set of C# extension methods to help working with nullable types by implementing the [Maybe monad](https://www.dotnetcurry.com/patterns-practices/1510/maybe-monad-csharp) on top of `T?`. This includes nullable value types (NVTs) and nullable reference types (NRTs).\n\n\u003e **Note**\\\n\u003e I consider this library experimental by now. Due to C#'s somewhat inconsistent implementation of NRTs, using a [dedicated maybe-like type](https://gist.github.com/bert2/eebc3dbb6c38599a041daaaec16467f8) will result in more user-friendly and safer code. [(Read more)](https://gist.github.com/bert2/2413ea125992fe59d66d24238cf9eba7#option-vs-nullable-reference-types)\n\n## Table of contents\n\n- [Prerequisites](#prerequisites)\n- [Usage examples](#usage-examples)\n- [Working with `Task\u003cT?\u003e`](#working-with-taskt)\n- [Known issues](#known-issues)\n  * [Fixing \"The call is ambiguous between...\"](#fixing-the-call-is-ambiguous-between)\n  * [Fixing conflicts with LINQ](#fixing-conflicts-with-linq)\n- [Method reference](#method-reference)\n  * [Core functionality](#core-functionality)\n  * [Support](#support)\n  * [Utility functions](#utility-functions)\n\n## Prerequisites\n\n- your project's `TargetFramework` must be `netcoreapp3.1` or `netstandard2.1`\n- [enabled nullable reference types](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references#nullable-contexts) (via `\u003cNullable\u003eenable\u003c/Nullable\u003e` in your `.csproj` file or `#nullable enable` in your `.cs` file)\n- import the namespaces:\n\n```csharp\n// required:\nusing Nullable.Extensions; // extension methods on `T?`\n\n// optional:\nusing Nullable.Extensions.Async; // async extension methods on `Task\u003cT?\u003e`\nusing Nullable.Extensions.Linq; // enables LINQ's query syntax on `T?`\n\n// utility:\nusing Nullable.Extensions.Util; // Nullable variants of framework functions. This is the way.\nusing static Nullable.Extensions.Util.TryParseFunctions; // helper functions to try-parse built-in types as `T?`\n```\n\nYou might encounter problems when you are using the extension methods from the namespaces `Async` or `Linq`. See [the sections below](#fixing-the-call-is-ambiguous-between) on how to resolve those.\n\nThe namespace `Nullable.Extensions.Util` is meant for functions that re-implement common framework behavior the \"nullable way\". For instance, instead of retrieving a value from a dictionary safely via `bool TryGetValue(K, out V)`, its nicer to use `V? TryGetValue(K)`. You feel there is one missing? Got an idea for a useful utility function? Please, let me know by creating an issue or a pull request!\n\n## Usage examples\n\nAll examples assume the namespace `Nullable.Extensions` has been imported.\n\nFiltering and parsing nullable user input:\n\n```csharp\nint num = GetUserInput() // returns `string?`\n    .Filter(s =\u003e s.All(char.IsDigit))\n    .Filter(s =\u003e s.Length \u003c 10)\n    .Map(int.Parse)\n    ?? throw new Exception(\"No input given or input was not numeric\");\n```\n\nWith the nullable variant of `int.TryParse()` this can be simplified:\n\n```csharp\nusing static Nullable.Extensions.Util.TryParseFunctions;\n\n// ...\n\nint num = GetUserInput(\"abc\")\n    .Bind(TryParseInt)\n    ?? throw new Exception(\"No input given or input was not numeric\");\n```\n\nUsing `Switch()` to provide a mapping and a default value in one go:\n\n```csharp\nint num = GetUserInput() // returns `string?`\n    .Bind(TryParseInt)\n    .Switch(notNull: n =\u003e n - 1, isNull: () =\u003e 0);\n```\n\nHowever, I'd prefer using an explicit `Map()` and the null-coalescing operator `??`:\n\n```csharp\nint num = GetUserInput()\n    .Bind(TryParseInt)\n    .Map(n =\u003e n - 1)\n    ?? 0;\n```\n\nWith `Switch()` and the `??` operator `null`-value handling can only be done at the end of a chain. Using `Else()`, however, we can also handle `null`s in the middle of a chain and replace them with alternative values:\n\n```csharp\nint num = GetUserInput(prompt: \"Enter a number:\")\n    .Bind(TryParseInt)\n    .Else(() =\u003e GetUserInput(prompt: \"A number PLEASE!\").Bind(TryParseInt)) // One more chance...\n    .Else(() =\u003e TryGetRandomNumber()) // If you don't care then I don't care.\n    .Map(n =\u003e n - 1)\n    ?? 0;\n```\n\n## Working with `Task\u003cT?\u003e`\n\nThe namespace `Nullable.Extensions.Async` contains asynchronous variants of most of the extension methods. Importing them enables the fluent API on `Task\u003cT?\u003e`.\n\n```csharp\nusing Nullable.Extensions;\nusing Nullable.Extensions.Async;\nusing Nullable.Extensions.Util;\nusing static Nullable.Extensions.Util.TryParseFunctions;\n\n// ...\n\npublic async Task\u003cUser?\u003e LoadUser(int id) =\u003e // ...\n\nstring userName = await requestParams // a `Dictionary\u003cstring, string\u003e`\n    .TryGetValue(\"userId\")\n    .Bind(TryParseInt)\n    .BindAsync(LoadUser)\n    .Map(u =\u003e u.Name)\n    ?? \"n/a\";\n```\n\nNote that you only have to `await` the result once at the very top of the method chain.\n\n## Known issues\n\n### Fixing \"The call is ambiguous between...\"\n\nWhen you are using extension methods from `Nullable.Extensions.Async`, you might occasionally encounter an error due to the compiler being unable to determine the correct overload:\n\n```csharp\nstring? msg = await Task\n    .FromResult\u003cstring?\u003e(\"world\")\n    .Map(s =\u003e $\"Hello, {s}!\"); // ERROR CS0121\n```\n\n\u003e CS0121: The call is ambiguous between the following methods or properties: 'Map\u003cT1, T2\u003e(T1?, Func\u003cT1, T2\u003e)' and 'Map\u003cT1, T2\u003e(Task\u003cT1?\u003e, Func\u003cT1, T2\u003e)'\n\nFortunately, this is easy to fix by assisting the type inference with an explicit type declaration on the lambda parameter:\n\n```csharp\nstring? msg = await Task\n    .FromResult\u003cstring?\u003e(\"world\")\n    .Map((string s) =\u003e $\"Hello, {s}!\"); // no error\n```\n\nThe fix is only needed under [certain circumstances](https://stackoverflow.com/questions/60754529/how-to-explain-this-call-is-ambiguous-error). Most of the time this fix should not be needed and you can let the compiler infer the type.\n\n### Fixing conflicts with LINQ\n\nWhen you are using extension methods from `Nullable.Extensions.Linq`, you might occasionally encounter situations where the compiler picks the wrong overload for `Select()`, `Where()`, or `SelectMany()`:\n\n```csharp\nvar xs = new[] { 1, 2, 3 }.Select(x =\u003e x.ToString()); // WARNING CS8634\n```\n\nIn the above example it was probably intended to use `System.Linq.Enumerable.Select()`, but the compiler chose the `Select()` extension on `T?` instead. A compiler warning might indicate this issue:\n\n\u003e CS8634: The type 'string?' cannot be used as type parameter 'T2' in the generic type or method 'SelectExt1.Select\u003cT1, T2\u003e(T1?, Func\u003cT1, T2\u003e)'. Nullability of type argument 'string?' doesn't match 'class' constraint.\n\nAgain, this is also easy to fix by assisting the type inference with an explicit type declaration on the lambda parameter:\n\n```csharp\nvar xs = new[] { 1, 2, 3 }.Select((int x) =\u003e x.ToString()); // no warning and correct `Select()`\n```\n\n## Method reference\n\n### Core functionality\n\n#### `T2? T1?.Bind\u003cT1, T2\u003e(Func\u003cT1, T2?\u003e binder)`\n\nEvaluates whether the `T1?` has a value. If so, it is provided to the `binder` function and its result is returned. Otherwise `Bind()` will return `null`.\n\n```csharp\nint? ParseInt(string s) =\u003e int.TryParse(s, out var i) ? (int?)i : null;\n\nint? num = Nullable(\"123\").Bind(ParseInt);\n```\n\nSimilar to `Map()` except that `binder` must return a nullable type.\n\n#### `T? T?.Filter\u003cT\u003e(Func\u003cT, bool\u003e predicate)`\n\nTurns `T?`s that don't satisfy the `predicate` function into `null`s. If `T?` already was `null` it will just be forwarded as is.\n\n```csharp\nint? even = Nullable(13).Filter(n =\u003e n % 2 == 0);\n// `even` will be `null`\n```\n\n#### `T2? T1?.Map\u003cT1, T2\u003e(Func\u003cT1, T2\u003e mapping)`\n\nEvaluates whether the `T1?` has a value. If so, it is provided to the `mapping` function and its result is returned. Otherwise `Map()` will return `null`.\n\n```csharp\nint? succ = Nullable(13).Map(n =\u003e n + 1);\n```\n\nSimilar to `Bind()` except that `mapping` must return a non-nullable type.\n\n### Support\n\n#### `T? T.AsNullable\u003cT\u003e()`\n\nConverts a `T` into a `T?`.\n\n```csharp\nstring str1 = \"hello\";\nstring? str2 = str1.AsNullable();\n```\n\nImplemented for completeness. Most of the time the implicit conversions from `T` to `T?` will be sufficient.\n\n#### `T? T?.Else\u003cT\u003e(Func\u003cT?\u003e onNull)`\n\nEvaluates whether the `T?` has a value. If so, it is simply forwarded untouched. When it's `null` the `onNull` function will be evaluated to calculate a replacement value. The result of `onNull()` might also be `null`.\n\n```csharp\nstring? yourMessage = null;\nstring? greeting = yourMessage.Else(() =\u003e \"Hello world!\");\n\nint? one = Nullable(1).Else(() =\u003e 13);\n```\n\n`Else()` is useful when implementing simple error handling. Its advantage over `Switch()` and `??` being that it can be used in the middle a method chain rather than only at the end of one.\n\n#### `T? Nullable\u003cT\u003e(T x)`\n\nCreates a nullable type from a value.\n\n```csharp\nusing static Nullable.Extensions.NullableClass;\nusing static Nullable.Extensions.NullableStruct;\n\n// ...\n\nstring? s = Nullable(\"hi\");\nint? i = Nullable(13);\n```\n\nMake sure to place the `using static ...` _inside_ your own namespace. Otherwise you will have to specifiy `T` explicitly (e.g. `Nullable\u003cint\u003e(13)`).\n\nImplemented for completeness. Most of the time the implicit conversions from `T` to `T?` will be sufficient.\n\n#### `T? Nullable\u003cT\u003e()`\n\nCreates a `null` of the specified type.\n\n```csharp\nusing static Nullable.Extensions.NullableClass;\nusing static Nullable.Extensions.NullableStruct;\n\n// ...\n\nstring? s = Nullable\u003cstring\u003e();\nint? i = Nullable\u003cint\u003e();\n```\n\nImplemented for completeness. Most of the time the explicit and implicit conversions from `null` to `T?` should be sufficient.\n\n#### `T2? T1?.Select\u003cT1, T2\u003e(Func\u003cT1, T2\u003e mapping)`\n\nAlias for `Map()`. Also enables LINQ's query syntax for `T?`.\n\n```csharp\nusing Nullable.Extensions.Linq;\n\n// ...\n\nint? i = from s in Nullable(\"3\")\n         select int.Parse(s);\n```\n\n#### `T3? T1?.SelectMany\u003cT1, T2, T3\u003e(Func\u003cT1, T2?\u003e binder)`\n\nAlias for `Bind()`.\n\n#### `T3? T1?.SelectMany\u003cT1, T2, T3\u003e(Func\u003cT1, T2?\u003e binder, Func\u003cT1, T2, T3\u003e mapping)`\n\nEnables LINQ's query syntax for `T?`.\n\n```csharp\nusing Nullable.Extensions.Linq;\n\n// ...\n\nint? ParseInt(string s) =\u003e int.TryParse(s, out var i) ? (int?)i : null;\n\nint? sum = from s in Nullable(\"2\")\n           from i1 in ParseInt(s)\n           from i2 in Nullable(3)\n           select i1 + i2;\n```\n\n#### `T2 T1?.Switch\u003cT1, T2\u003e(Func\u003cT1, T2\u003e notNull, Func\u003cT2\u003e isNull)`\n\nSwitches on a nullable type and returns the result of one the provided functions. The `notNull` function is executed in case the `T?` is not null. The `isNull` function otherwise.\n\n```csharp\nbool success = Nullable(\"abc\").Switch(\n    notNull: s =\u003e true,\n    isNull: () =\u003e false);\n```\n\n`Switch()` is supposed to be used to terminate a chain of `T?` extension methods. It shouldn't be needed too often, though. Most of the time the null-coalescing operator `??` should be sufficient.\n\n#### `T? T?.Tap\u003cT\u003e(Action\u003cT\u003e effect)`\n\nExecutes a side-effect in case the `T?` has a value and then returns it unchanged. This works similar to tapping a phone line. Also useful during debugging, because it can be safely added to method chains for additional break points.\n\n```csharp\nbool s_was_null = true;\nbool i_was_null = true;\n\nstring? s = Nullable(\"hi\").Tap(s =\u003e s_was_null = false);\nint? i = Nullable\u003cint\u003e().Tap(i =\u003e i_was_null = false);\n\n// `s_was_null` is now `false`\n// `i_was_null` is still `true`\n```\n\n#### `IEnumerable\u003cT\u003e T?.ToEnumerable\u003cT\u003e()`\n\nConverts `T?` into an `IEnumerable\u003cT\u003e` with a single item in case the `T?` has a value. Otherwise the `IEnumerable\u003cT\u003e` will be empty.\n\n```csharp\nIEnumerable\u003cint\u003e singleton = Nullable(13).ToEnumerable();\nIEnumerable\u003cstring\u003e empty = Nullable\u003cstring\u003e().ToEnumerable();\n```\n\n#### `T? IEnumerable\u003cT\u003e.ToNullable\u003cT\u003e()`\n\nConverts a singleton `IEnumerable\u003cT\u003e` into a `T?` that is `null` case the `IEnumerable\u003cT\u003e` is empty. Otherwise the result will be the `IEnumerable\u003cT\u003e`'s only item. Throws when the `IEnumerable\u003cT\u003e` contains more than one item.\n\n```csharp\nint? nullValue = Enumerable.Empty\u003cint\u003e().ToNullable();\nstring? nonNullValue = new[] { \"hi\" }.ToNullable();\nnew[] { 1, 2, 3 }.ToNullable(); // throws `InvalidOperationException`\n```\n\n#### `T? T?.Where\u003cT\u003e(Func\u003cT, bool\u003e predicate)`\n\nAlias for `Filter()`. Also enables LINQ's query syntax for `T?`.\n\n```csharp\nusing Nullable.Extensions.Linq;\n\n// ...\n\nstring msg = from s in Nullable(\"hi!\")\n             where s.Length \u003e 0\n             select s;\n```\n\n### Utility functions\n\nGot an idea for a useful utility function? Please, let me know by creating an issue or a pull request!\n\n#### `T? IDictionary\u003cK, V\u003e.TryGetValue\u003cK, V\u003e(K key)`\n\nGets the value associated with the specified key, or `null` if the key is not present.\n\n```csharp\nusing Nullable.Extensions.Util;\n\n// ...\n\nvar nums = new Dictionary\u003cstring, int\u003e { [\"lucky\"] = 13 };\nvar luckyNumber = nums.TryGetValue(\"lucky\");\nvar nullValue = nums.TryGetValue(\"unlucky\");\n```\n\n#### `T? TryParse*(string value)`\n\nA family of functions for safely parsing strings. Can be used as a replacement for `bool int.TryParse(string, out int)` and all its variants.\n\nThe following types are supported: `bool`, `byte`, `sbyte`, `char`, `decimal`, `double`, `float`, `int`, `uint`, `long`, `ulong`, `short`, `ushort`, `DateTime`, `DateTimeOffset`, `TimeSpan`, and `Guid`.\n\n```csharp\nusing static Nullable.Extensions.Util.TryParseFunctions;\n\n// ...\n\nint? one = TryParseInt(\"1\");\nulong? manyNines = TryParseULong(\"9999999999999999999\");\nTimeSpan? time = TryParseTimeSpan(\"13:12\");\nDateTime? nullValue = TryParseDateTime(\"yesterday\");\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbert2%2Fnullable.extensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbert2%2Fnullable.extensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbert2%2Fnullable.extensions/lists"}