CSharpFunctionalExtensions
Functional extensions for C#
Install / Use
/learn @vkhorikov/CSharpFunctionalExtensionsREADME
Functional Extensions for C#
This library helps write code in more functional way. To get to know more about the principles behind it, check out the Applying Functional Principles in C# Pluralsight course.
Installation
Available on NuGet
dotnet add package CSharpFunctionalExtensions
or
PM> Install-Package CSharpFunctionalExtensions
Also available as a strong named assembly (big thanks to bothzoli who made it possible!).
On NuGet
dotnet add package CSharpFunctionalExtensions.StrongName
Core Concepts
Get rid of primitive obsession
Result<CustomerName> name = CustomerName.Create(model.Name);
Result<Email> email = Email.Create(model.PrimaryEmail);
Result result = Result.Combine(name, email);
if (result.IsFailure)
return Error(result.Error);
var customer = new Customer(name.Value, email.Value);
Make nulls explicit with the Maybe type
Maybe<Customer> customerOrNothing = _customerRepository.GetById(id);
if (customerOrNothing.HasNoValue)
return Error("Customer with such Id is not found: " + id);
Compose multiple operations in a single chain
return _customerRepository.GetById(id)
.ToResult("Customer with such Id is not found: " + id)
.Ensure(customer => customer.CanBePromoted(), "The customer has the highest status possible")
.Tap(customer => customer.Promote())
.Tap(customer => _emailGateway.SendPromotionNotification(customer.PrimaryEmail, customer.Status))
.Finally(result => result.IsSuccess ? Ok() : Error(result.Error));
Wrap multiple operations in a TransactionScope
return _customerRepository.GetById(id)
.ToResult("Customer with such Id is not found: " + id)
.Ensure(customer => customer.CanBePromoted(), "The customer has the highest status possible")
.WithTransactionScope(customer => Result.Success(customer)
.Tap(customer => customer.Promote())
.Tap(customer => customer.ClearAppointments()))
.Tap(customer => _emailGateway.SendPromotionNotification(customer.PrimaryEmail, customer.Status))
.Finally(result => result.IsSuccess ? Ok() : Error(result.Error));
API Examples
Maybe
Explicit Construction
Use case: Creating a new Maybe containing a value
Maybe<string> apple = Maybe<string>.From("apple");
// or
Maybe<string> apple = Maybe.From("apple"); // type inference
// or
var apple = Maybe.From("apple");
None/No Value
Use case: Replacing null or the
Null Object Pattern for representing 'missing' data.
int storeInventory = ...
Maybe<string> fruit = storeInventory > 0
? Maybe<string>.From("apple")
: Maybe<string>.None;
// or where the generic type is a reference type
Maybe<string> fruit = null;
// or where the generic type is a value type
Maybe<int> fruit = default;
Implicit Conversion
Use case: Easily creating a Maybe from a value
// Constructing a Maybe
Maybe<string> apple = "apple"; // implicit conversion
// Or as a method return value
Maybe<string> GetFruit(string fruit)
{
if (string.IsNullOrWhiteSpace(fruit))
{
return Maybe<string>.None;
}
return fruit; // implicit conversion
}
Equality
Use case: Comparing Maybes or values without knowledge of the inner value of the Maybes
Maybe<string> apple = "apple";
Maybe<string> orange = "orange";
string alsoOrange = "orange";
Maybe<string> noFruit = Maybe<string>.None;
Console.WriteLine(apple == orange); // false
Console.WriteLine(apple != orange); // true
Console.WriteLine(orange == alsoOrange); // true
Console.WriteLine(alsoOrange == noFruit); // false
ToString
Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;
Console.WriteLine(apple.ToString()); // "apple"
Console.WriteLine(noFruit.ToString()); // "No value"
GetValueOrThrow
Use case: Procedurally accessing the inner value of the Maybe
Note: Calling this will throw a InvalidOperationException if there is no value
Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;
Console.WriteLine(apple.GetValueOrThrow()); // "apple";
Console.WriteLine(noFruit.GetValueOrThrow()); // throws InvalidOperationException !!
Console.WriteLine(noFruit.GetValueOrThrow(new CustomException())); // throws CustomException !!
HasValue and HasNoValue
Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly
void Response(string fruit)
{
Console.WriteLine($"Yum, a {fruit} 😀");
}
Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;
if (apple.HasValue)
{
Response(apple.Value); // safe to access since we checked above
}
if (noFruit.HasNoValue)
{
Response("We're all out of fruit 😢");
}
GetValueOrDefault
Use case: Safely accessing the inner value, without checking if there is one, by providing a fallback if no value exists
void Response(string fruit)
{
Console.WriteLine($"It's a {fruit}");
}
Maybe<string> apple = "apple";
Maybe<string> unknownFruit = Maybe<string>.None;
string appleValue = apple.GetValueOrDefault("banana");
string unknownFruitValue = unknownFruit.GetValueOrDefault("banana");
Response(appleValue); // It's a apple
Response(unknownFruitValue); // It's a banana
Where
Use case: Converting a Maybe with a value to a Maybe.None if a condition isn't met
Note: The predicate passed to Where (ex )
bool IsMyFavorite(string fruit)
{
return fruit == "papaya";
}
Maybe<string> apple = "apple";
Maybe<string> favoriteFruit = apple.Where(IsMyFavorite);
Console.WriteLine(favoriteFruit.ToString()); // "No value"
Map
Use case: Transforming the value in the Maybe, if there is one, without needing to check if the value is there
Note: the delegate (ex CreateMessage) passed to Maybe.Map() is only executed if the Maybe has an inner value
string CreateMessage(string fruit)
{
return $"The fruit is a {fruit}";
}
Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;
Console.WriteLine(apple.Map(CreateMessage).Unwrap("No fruit")); // "The fruit is a apple"
Console.WriteLine(noFruit.Map(CreateMessage).Unwrap("No fruit")); // "No fruit"
Select
Alias: Maybe.Select() is an alias of Maybe.Map()
Bind
Use case: Transforming from one Maybe into another Maybe
(like Maybe.Map but it transforms the Maybe instead of the inner value)
Note: the delegate (ex MakeAppleSauce) passed to Maybe.Bind() is only executed if the Maybe has an inner value
Maybe<string> MakeAppleSauce(Maybe<string> fruit)
{
if (fruit == "apple") // we can only make applesauce from apples 🍎
{
return "applesauce";
}
return Maybe<string>.None;
}
Maybe<string> apple = "apple";
Maybe<string> banana = "banana";
Maybe<string> noFruit = Maybe<string>.None;
Console.WriteLine(apple.Bind(MakeAppleSauce).ToString()); // "applesauce"
Console.WriteLine(banana.Bind(MakeAppleSauce).ToString()); // "No value"
Console.WriteLine(noFruit.Bind(MakeAppleSauce).ToString()); // "No value"
SelectMany
Alias: Maybe.SelectMany() is an alias of Maybe.Bind()
Choose
Use case: Filter a collection of Maybes to only the ones that have a value, and then return the value for each, or map that value to a new one
Note: the delegate passed to Maybe.Choose() is only executed on the Maybes of the collection with an inner value
IEnumerable<Maybe<string>> unknownFruits = new[] { "apple", Maybe<string>.None, "banana" };
IEnumerable<string> knownFruits = unknownFruits.Choose();
IEnumerable<string> fruitResponses = unknownFruits.Choose(fruit => $"Delicious {fruit}");
Console.WriteLine(string.Join(", ", knownFruits)) // "apple, banana"
Console.WriteLine(string.Join(", ", fruitResponses)) // "Delicious apple, Delicious banana"
Execute
Use case: Safely executing a void (or Task) returning operation on the Maybe inner value
without checking if there is one
Note: the Action (ex PrintFruit) passed to Maybe.Execute() is only executed if the Maybe has an inner value
void PrintFruit(string fruit)
{
Console.WriteLine($"This is a {fruit}");
}
Maybe<string> apple = "apple";
Maybe<string> noFruit = Maybe<string>.None;
apple.Execute(PrintFruit); // "This is a apple"
noFruit.Execute(PrintFruit); // no output to the console
ExecuteNoValue
Use case: Executing a void (or Task) returning operation when the Maybe has no value
void LogNoFruit(string fruit)
{
Console.WriteLine($"There are no {fruit}");
}
Maybe<string> apple = "apple";
Maybe<string> banana = Maybe<string>.None;
apple.ExecuteNoValue(() => LogNoFruit("apple")); // no output to console
banana.ExecuteNoValue(() => LogNoFruit("banana")); // "There are no banana"
Or
Use case: Supplying a fallback value Maybe or value in the case that the Maybe has no inner value
Note: The fallback `Func<T
Related Skills
node-connect
349.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.5kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
