Frostware.Result
1.0.0
dotnet add package Frostware.Result --version 1.0.0
NuGet\Install-Package Frostware.Result -Version 1.0.0
<PackageReference Include="Frostware.Result" Version="1.0.0" />
paket add Frostware.Result --version 1.0.0
#r "nuget: Frostware.Result, 1.0.0"
// Install Frostware.Result as a Cake Addin #addin nuget:?package=Frostware.Result&version=1.0.0 // Install Frostware.Result as a Cake Tool #tool nuget:?package=Frostware.Result&version=1.0.0
Frostware.Result
A simple implementation of a functional result type in C#
Table of contents
What is a result type, and why use it?
The result type is an alternative to a try/catch that aims to remove the need for nulls and force handling of failed cases.
By having your methods return Result, it allows you to pattern match (switch) over that result at execution. You can then supply an implementation for every possible state of your method.
How to use
This is the main result class:
public class Result
{
// Pass inherits Result
public static Result Pass() => new Pass();
// Pass<T> inherits Pass
public static Result Pass<T>(T value) => new Pass<T>(value);
//Fail inherits Result
public static Result Fail(string errorMessage = "") => new Fail(errorMessage);
//Fail<T> inherits Fail
public static Result Fail<T>(T value, string errorMessage = default) => new Fail<T>(value, errorMessage);
}
A few things to note here:
- Pass can either be empty or contain a value.
- Pass<T> is a Pass, Pass is a Result. This distinction is important for pattern matching.
- Fail works the same as Pass but has an added optional error message.
Method Decleration
Simply make your method return the type "Result" and use the static helper methods on the Result class.
/// <summary>
/// Example Method
/// </summary>
/// <returns>Pass : int, Fail : int, Fail : foo</returns>
public Result ExampleMethod()
{
if (condition1)
return Result.Pass(20); //returns Pass<int> { Value = 20 }
else if (condition2)
return Result.Pass(); //returns Pass {}
else if (condition3)
return Result.Fail(30, "Oporation Failed"); //returns Fail<Int> { Value = 30, ErrorMessage = "Oporation Failed" }
else if (condition4)
return Result.Fail(new Foo()); //returns Fail<Foo> { Value = Foo {}, ErrorMessage = "" }
else
return Result.Fail(); //returns Fail {ErrorMessage = ""}
}
You may of noticed that we are returning both a Fail<int> and a Fail<Foo>. You can do that! Since you should be handling all possible states of your result this won't be a problem at all. Just make sure your method's summery is clear about how it works and what it may return. See the <returns /> tag for an example
Result Handling
It is recomended to pattern match over the result, either with a switch statement,
switch(ExampleMethod())
{
case Pass<int> x:
//triggered when result is a pass and contains a value of int
break;
case Pass<Foo> x:
//triggered when result is a pass and contains a value of Foo
break;
case Pass _: //you can ommit the "_" as of .NET 5
//triggered when result is a pass of any type
break;
case Fail<int> x:
//triggered when result is a fail and contains a value of int
break;
case Fail<Foo> x:
//triggered when result is a fail and contains a value of Foo
break;
case Fail _: //you can ommit the "_" as of .NET 5
//triggered when result is a fail of any type
break;
}
or a switch expression.
string result = ExampleMethod() switch
{
Pass<int> x => $"This is a pass that contains the int {x.Value}",
Pass<Foo> x => $"This is a pass that contains an instance of the class Foo {x.Value}",
Pass => "This is an empty pass",
Fail<int> x => $"This failed with message {x.ErrorMessage} and contains the int {x.Value}",
Fail<Foo> x => $"This failed with message {x.ErrorMessage} and contains an instance of the class foo {x.Value}",
Fail x => $"This failed with message {x.ErrorMessage}"
}
Note that if you check for Pass before Pass<int> the compiler will throw an error stating that the pattern or case has already been handled. This is do to Pass<int> being a Pass. This is intentional, it means that if you just want to check if an oporation passed and don't care about the value, you can just check for Pass. Same applies to Fail.
Product | Versions 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.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. |
-
.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 |
---|---|---|
1.0.0 | 352 | 2/19/2021 |