FunctionalRecords 1.6.0
dotnet add package FunctionalRecords --version 1.6.0
NuGet\Install-Package FunctionalRecords -Version 1.6.0
<PackageReference Include="FunctionalRecords" Version="1.6.0" />
<PackageVersion Include="FunctionalRecords" Version="1.6.0" />
<PackageReference Include="FunctionalRecords" />
paket add FunctionalRecords --version 1.6.0
#r "nuget: FunctionalRecords, 1.6.0"
#:package FunctionalRecords@1.6.0
#addin nuget:?package=FunctionalRecords&version=1.6.0
#tool nuget:?package=FunctionalRecords&version=1.6.0
FunctionalRecords
C# library (.NET 6, 7, 8+) with functional records: Maybe, Choice, ValueRecord (value object), Result
Install from nuget
dotnet add package FunctionalRecords
Support for System.Text.Json serialization
dotnet add package FunctionalRecords.Serialization.Json
Hot to use
ℹ All examples are available in /examples directory
Maybe<T>
Maybe<T> is a readonly record struct of <T>. Can hold a value or be None. Has members:
bool IsSome- true if setbool IsNone- false if not setvoid Match(Action<T>, Action)- Calls first parameterAction<T>ifIsSome, and ifIsNonecalls second parameterActionvoid Match(Action<T>)- CallsAction<T>ifIsSomevoid Match(Action)- CallsActionifIsNoneT? ValueOrDefault- returns value or default value if IsNoneT Value- returns value if IsSome, otherwise throwsInvalidOperationExceptionstatic Maybe<T> None- returnsNoneofMaybe<T>static Maybe<T> From(T)- returnsMaybe<T>from valuestatic implicit operator Maybe<T>(T)- converts value ofTtoMaybe<T>
Maybe<string> s0 = Maybe<string>.None;
Maybe<string> s1 = null;
Maybe<string> s2 = Maybe<string>.From(null);
Maybe<string> s3 = Maybe<string>.From("s");
Maybe<string> s = "a";
if (s.IsSome) // true when set
{
Console.WriteLine($"S value: {s.Value}");
}
if (s.IsNone) // true when not set
{
Console.WriteLine("S is not set");
}
string valueOrDefault = s.ValueOrDefault; // returns value if present or default of <T>
string value = s.Value; // !!!⚠!!! Exception will be thrown if IsNone
s.Match(() => Console.WriteLine("s is set")); // action called only when s has value
int sLength = s.Match(
some: value => value.Length,
none: () => -1
);
Console.WriteLine($"sLength (-1 if null): {sLength}");
Maybe<T> is implementing (sinve v1.5)
public interface IMaybe
{
bool IsSome { get; }
bool IsNone { get; }
}
Result, Result<T> and Result<T,TFailureType>
Result and Result<T> are readonly record structs. Both can be success or failure.
Result implements IResult members:
bool IsSuccess- true or falsebool IsFailure- opposite ofIsSuccessIReadOnlyList<string> Errors- error list, empty by default, cannot be nullMaybe<Exception> Exception- optional exceptionEnsureSuccess()- throws exception in case of failure
Result<T> implements Result<T> members:
bool IsSuccess- true or falsebool IsFailure- opposite ofIsSuccessIReadOnlyList<string> Errors- error list, empty by default, cannot be nullMaybe<Exception> Exception- optional exceptionT Value- balue ofTEnsureSuccess()- throws exception in case of failure
Result<T, TFailureType> implements IResult<T, TFailureType> members:
bool IsSuccess- true or falsebool IsFailure- opposite ofIsSuccessIReadOnlyList<string> Errors- error list, empty by default, cannot be nullMaybe<Exception> Exception- optional exceptionT Value- balue ofT- Maybe<TFailureType> FailureType - optinal failure type
EnsureSuccess()- throws exception in case of failure
Result static methods:
static Result Success()static Result<TValue> Success<TValue>(TValue value)static Result<TValue, TFailureType> Success<TValue, TFailureType>(TValue value)static Result Failure()static Result Failure(params string[] errors)static Result Failure(IEnumerable<string> errors)static Result Failure(Maybe<Exception> exception, params string[] errors)static Result Failure(Maybe<Exception> exception, IEnumerable<string> errors)static Result Failure(Maybe<Exception> exception, IReadOnlyList<string> errors)static Result<TValue> Failure<TValue>(params string[] errors)static Result<TValue> Failure<TValue>(IEnumerable<string> errors)static Result<TValue> Failure<TValue>(IReadOnlyList<string> errors)static Result<TValue> Failure<TValue>(Exception exception, IEnumerable<string> errors)static Result<TValue> Failure<TValue>(Exception exception, params string[] errors)static Result<TValue> Failure<TValue>(Exception exception, IReadOnlyList<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(params string[] errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(IEnumerable<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(IReadOnlyList<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(Exception exception, IEnumerable<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(Exception exception, params string[] errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(Exception exception, IReadOnlyList<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(TFailureType failure)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(TFailureType failure, params string[] errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(TFailureType failure, IEnumerable<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(TFailureType failure, IReadOnlyList<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(TFailureType failure, Exception exception, IEnumerable<string> errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(TFailureType failure, Exception exception, params string[] errors)static Result<TValue, TFailureType> Failure<TValue, TFailureType>(TFailureType failure, Exception exception, IReadOnlyList<string> errors)
Interfaces:
public interface IResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
IReadOnlyList<string> Errors { get; }
Maybe<Exception> Exception { get; }
void EnsureSuccess();
}
public interface IResult<TValue> : IResult
{
Maybe<TValue> Value { get; }
}
public interface IResult<TValue, TFailureType> : IResult<TValue> where TFailureType : Enum
{
Maybe<TFailureType> FailureType { get; }
}
Usage:
Result rSuccess = Result.Success();
Result rFailure = Result.Failure();
rFailure = Result.Failure("Error description 1", "Error description 2");
rFailure = Result.Failure(new FileNotFoundException("File missing", "myFile.txt"), "Error description 1", "Error description 2");
Result<int> rSuccessWithValue = Result.Success<int>(4);
Result<int> rFailureWithNoValue = Result.Failure<int>("Something went wrong");
Console.WriteLine(rSuccessWithValue.Value.ValueOrDefault);
Result<int> rFailureWithValue = Result.Failure<int>(new InvalidOperationException("message"), "error 1", "error 2", "error3");
Result<string, FileFailures> r = Result.Failure<string, FileFailures>(FileFailures.FileTooLarge | FileFailures.WrongFileExtension, "Wrong extension and size");
r.Is(FileFailures.FileTooLarge); // true
r.Is(FileFailures.WrongFileExtension); // true
r.Is(FileFailures.FileNotFound); // false
FileFailures failureType = r.FailureType.Value;
// FileFailures enum used in example above
[Flags]
public enum FileFailures
{
FileNotFound,
FileTooLarge,
WrongFileExtension
}
ValueRecord<T>
ValueRecord is abstract record that can be used to implement DDD-like Value Objects.
Members:
ValueRecord(T value)- constructorT Valueholds valueabstract IEnumerable<string> GetValidationErrors(T value)- Must be implemented and should return validation errorsvirtual void AfterSuccessfulValidation()- Can be implemented to throw custom exception after validation passesValidate(T value)- called in constructor- If
GetValidationErrorsreturns anything newFunctionalRecords.ValidationExceptionwill be thrown - If validation passes
AfterSuccessfulValidationis called
- If
virtual T TransformValue(T value)- optionally value can be transformed before validated and assigned toValueproperty- If implemented transformed value is passed to
Validatemethod
- If implemented transformed value is passed to
Simple example where we want to have integer record that is always greater than zero:
public record PositiveInteger : ValueRecord<int>
{
public PositiveInteger(int value) : base(value)
{
}
protected override IEnumerable<string> GetValidationErrors(int value)
{
if (value < 1)
{
yield return "Value cannot be less than 1";
}
}
public static implicit operator PositiveInteger(int i) => new(i);
}
More complex example where persons last name is transformed to be upper-case:
// ⚠ must be a record and NOT a class
public record PersonName : ValueRecord<(string FirstName, string LastName)>
{
public PersonName((string FirstName, string LastName) value) : base(value)
{
}
protected override IEnumerable<string> GetValidationErrors((string FirstName, string LastName) value)
{
// abstract method
// if something is returned from this method FunctionalRecords.ValidationException will be thrown
if (string.IsNullOrEmpty(value.FirstName))
{
yield return "FirstName not set";
}
if (string.IsNullOrEmpty(value.LastName))
{
yield return "LastName not set";
}
}
protected override void AfterSuccessfulValidation()
{
// virtual method, optionally throw some other exception here
// called only if GetValidationErrors does not return anything
}
protected override (string FirstName, string LastName) TransformValue((string FirstName, string LastName) value)
{
// virtual method called before validation
// optionally transform object
return (value.FirstName, value.LastName?.ToUpper());
}
// optionally implement conversion
public static implicit operator PersonName ((string, string) value) => new(value);
// optionally override to string
public override string ToString() => $"Person: {Value.LastName}, {Value.FirstName}";
}
Choice<T1, T2>
Records:
Choice<T1, T2>Choice<T1, T2, T3>Choice<T1, T2, T3, T4>Choice<T1, T2, T3, T4, T5>Choice<T1, T2, T3, T4, T5, T6>
Choice records are readonly record structs. They hold one of given values.
Members:
static Choice<T1, T2> From(T1 value)- value cannot be nullstatic Choice<T1, T2> From(T2 value)- value cannot be nullValue- value assigned to choiceType GetChosenType()- returntypeof(T1)ortypeof(T2)Is<T>()- returnstrueifTis of give type ofValuevoid MatchMatch(Action<T1> matchT1, Action<T2> matchT2)- calls one of the action, dependeing on chosen typeTResult Match<TResult>(Func<T1, TResult> matchT1, Func<T2, TResult> matchT2)- calls one of the functions, dependeing on chosen type
Choice<string, int> stringOrInt1 = 4;
Choice<string, int> stringOrInt2 = "abcd";
Choice<string, int> stringOrInt3 = Choice<string, int>.From("a");
Choice<string, int> stringOrInt4 = Choice<string, int>.From(3);
Choice<int, float, double> choice3 = Choice<int, float, double>.From(3);
Choice<int, float, double, decimal> choice4 = Choice<int, float, double, decimal>.From(3);
Choice<int, float, double, decimal, string> choice5 = Choice<int, float, double, decimal, string>.From(3);
Choice<int, float, double, decimal, string, bool> choice6 = 3;
Console.WriteLine(choice6.Value); // Writes "3";
// call Func<T1, TResult> or Func<T2, TResult>
int stringLength1 = stringOrInt1.Match(
s => s.Length,
i => i
);
bool isInt = stringOrInt1.Is<int>(); // true
bool isString = stringOrInt1.Is<string>(); // false
// call Action<T1> or Action<T2>
stringOrInt2.Match(
s => Console.WriteLine($"{nameof(stringOrInt2)} is string {s}"),
i => Console.WriteLine($"{nameof(stringOrInt2)} is int {i}")
);
Type t = stringOrInt2.GetChosenType(); // returns typeof(string)
Serialization
Package FunctionalRecords.Serialization.Json needs to be referenced if (de)serialization of records is required.
using namespace FunctionalRecords.Serialization.Json;
public static class SerializationExample
{
public class JsonTestObj
{
public ValueRecordExample.PersonName PersonName { get; set; }
public Maybe<int> MaybeInt1 { get; set; }
public Maybe<int> MaybeInt2 { get; set; }
public Result<Guid> Result { get; set; }
public Choice<string, int> StringOrInt { get; set; }
}
public static void Example()
{
JsonTestObj t = new JsonTestObj
{
PersonName = ("Jane", "Doe"),
StringOrInt = "Somethig",
Result = Result.Success(Guid.NewGuid()),
MaybeInt1 = Maybe<int>.None,
MaybeInt2 = -5548
};
JsonSerializerOptions serializerOptions = new JsonSerializerOptions();
serializerOptions.WriteIndented = true;
serializerOptions.AddFunctionalRecordsConverters();
string json = JsonSerializer.Serialize(t, serializerOptions);
Console.WriteLine(json);
}
}
ValueTuples are serialized as JSON arrays, Maybe is serialized as null or as value, Choice as JSON array where
first element is selected type (1-indexed) and second element is value.
Console output from previous code:
{
"PersonName": {
"Value": [
"Jane",
"DOE"
]
},
"MaybeInt1": null,
"MaybeInt2": -5548,
"Result": {
"IsSuccess": true,
"Exception": null,
"Errors": [],
"Value": "c8b7466e-2260-4bc9-b2e9-b26122ee9dd8"
},
"StringOrInt": {
"$choiceType": "String",
"value": "Somethig"
}
}
Change History
- 1.6.0 - added check for throwing
ExceptionfromIResultwhenEnsureSuccessis called, added .NET 8 as one of the target frameworks - 1.5.0 - added
IMaybeinherited byMaybe<T>, added .NET 7 as one of the target frameworks - 1.4.0 - changed serialization of
Choiceso it's serialized as object (change is backward compatible) - 1.3.0 - added method
EnsureSuccess()onIResult,IResult<T>andIResult<T, TFailureType> - 1.2.0 - added interfaces
IResult,IResult<T>andIResult<T, TFailureType> - 1.1.0 - added
Result<T, TFailureType> - 1.0.0 - initial version
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. 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 is compatible. 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 is compatible. 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. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net6.0
- No dependencies.
-
net7.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on FunctionalRecords:
| Package | Downloads |
|---|---|
|
FunctionalRecords.Serialization.Json
JsonConverters for FunctionalRecords |
GitHub repositories
This package is not used by any popular GitHub repositories.