Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Functional Extensions for C#

Build StatusNuGet downloadsGitHub license

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

Core Concepts

Get rid of primitive obsession

Result<CustomerName>name=CustomerName.Create(model.Name);Result<Email>email=Email.Create(model.PrimaryEmail);Resultresult=Result.Combine(name,email);if(result.IsFailure)returnError(result.Error);varcustomer=newCustomer(name.Value,email.Value);

Make nulls explicit with the Maybe type

Maybe<Customer>customerOrNothing=_customerRepository.GetById(id);if(customerOrNothing.HasNoValue)returnError("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");// orMaybe<string>apple=Maybe.From("apple");// type inference// orvarapple=Maybe.From("apple");

None/No Value

Use case: Replacing null or the Null Object Pattern for representing 'missing' data.

intstoreInventory= ...Maybe<string> fruit =storeInventory>0?Maybe<string>.From("apple"):Maybe<string>.None;// or where the generic type is a reference typeMaybe<string>fruit=null;// or where the generic type is a value typeMaybe<int>fruit=default;

Implicit Conversion

Use case: Easily creating a Maybe from a value

// Constructing a MaybeMaybe<string>apple="apple";// implicit conversion// Or as a method return valueMaybe<string>GetFruit(stringfruit){if(string.IsNullOrWhiteSpace(fruit)){returnMaybe<string>.None;}returnfruit;// 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";stringalsoOrange="orange";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple==orange);// falseConsole.WriteLine(apple!=orange);// trueConsole.WriteLine(orange==alsoOrange);// trueConsole.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(newCustomException()));// throws CustomException !!

HasValue and HasNoValue

Use case: Procedurally checking if the Maybe has a value, usually before accessing the value directly

voidResponse(stringfruit){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

voidResponse(stringfruit){Console.WriteLine($"It's a {fruit}");}Maybe<string>apple="apple";Maybe<string>unknownFruit=Maybe<string>.None;stringappleValue=apple.GetValueOrDefault("banana");stringunknownFruitValue=unknownFruit.GetValueOrDefault("banana");Response(appleValue);// It's a appleResponse(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 )

boolIsMyFavorite(stringfruit){returnfruit=="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

stringCreateMessage(stringfruit){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";}returnMaybe<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(", ",fruits))// "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

voidPrintFruit(stringfruit){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

voidLogNoFruit(stringfruit){Console.WriteLine($"There are no {fruit}");}Maybe<string>apple="apple";Maybe<string>banana=Maybe<string>.None;apple.ExecuteNoValue(()=>LogNoFruit("apple"));// no output to consolebanana.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> (ex () => "banana") will only be executed if the Maybe has no inner value

Maybe<string>apple="apple";Maybe<string>banana="banana";Maybe<string>noFruit=Maybe<string>.None;Console.WriteLine(apple.Or(banana).ToString());// "apple"Console.WriteLine(noFruit.Or(()=>banana)).ToString());// "banana"Console.WriteLine(noFruit.Or("banana").ToString());// "banana"Console.WriteLine(noFruit.Or(()=>"banana").ToString());// "banana"

Match

Use case: Defining two operations to perform on a Maybe. One to be executed if there is an inner value, and the other to executed if there is not

Maybe<string>apple="apple";Maybe<string>noFruit=Maybe<string>.None;// Void returning Matchapple.Match(
fruit =>Console.WriteLine($"It's a {fruit}"),()=>Console.WriteLine("There's no fruit"));// Mapping MatchstringfruitMessage=noFruit.Match(
fruit =>$"It's a {fruit}",()=>"There's no fruit"));Console.WriteLine(fruitMessage);// "There's no fruit"

TryFirst and TryLast

Use case: Replacing .FirstOrDefault() and .LastOrDefault() so that you can return a Maybe instead of a null or value type default value (like 0, false) when working with collections

IEnumerable<string>fruits=new[]{"apple","coconut","banana"};Maybe<string>firstFruit=fruits.TryFirst();Maybe<string>probablyABanana=fruits.TryFirst(fruit =>fruit.StartsWith("ba"));Maybe<string>aPeachOrAPear=fruits.TryFirst(fruit =>fruit.StartsWith("p"));Console.WriteLine(firstFruit.ToString());// "apple"Console.WriteLine(probablyABanana.ToString());// "banana"Console.WriteLine(aPeachOrAPear.ToString());// "No value"Maybe<string>lastFruit=fruits.TryLast();Maybe<string>anAppleOrApricot=fruits.TryLast(fruit =>fruit.StartsWith("a"));Console.WriteLine(lastFruit.ToString());// "banana"Console.WriteLine(anAppleOrApricot.ToString());// "apple"

TryFind

Use case: Safely getting a value out of a Dictionary

Dictionary<string,int>fruitInventory=new(){{"apple",10},{"banana",2}};Maybe<int>appleCount=fruitInventory.TryFind("apple");Maybe<int>kiwiCount=fruitInventory.TryFind("kiwi");Console.WriteLine(appleCount.ToString());// "10"Console.WriteLine(kiwiCount.ToString());// "No value"

ToResult

Use case: Representing the lack of an inner value in a Maybe as a failed operation

Note: See Result section below

Maybe<string>fruit="banana";Maybe<string>noFruit=Maybe<string>.None;stringerrorMessage="There was no fruit to give";Result<string>weGotAFruit=fruit.ToResult(errorMessage);Result<string>failedToGetAFruit=noFruit.ToResult(errorMessage);Console.WriteLine(weGotAFruit.Value);// "banana"Console.WriteLine(failedToGetAFruit.Error);// "There was no fruit to give"

Result

Explicit Construction: Success and Failure

Use case: Creating a new Result in a Success or Failure state

recordFruitInventory(stringName,intCount);Result<FruitInventory>appleInventory=Result.Success(newFruitInventory("apple",4));Result<FruitInventory>failedOperation=Result.Failure<FruitInventory>("Could not find inventory");ResultsuccessInventoryUpdate=Result.Success();

Conditional Construction: SuccessIf and FailureIf

Use case: Creating successful or failed Results based on expressions or delegates instead of if/else statements or ternary expressions

boolonTropicalIsland=true;ResultfoundCoconut=Result.SuccessIf(onTropicalIsland,"These trees seem bare 🥥");ResultfoundGrapes=Result.FailureIf(()=>onTropicalIsland,"No grapes 🍇 here");// orboolisNewShipmentDay=true;Result<FruitInventory>appleInventory=Result.SuccessIf(isNewShipmentDay,newFruitInventory("apple",4),"No 🍎 today");Result<FruitInventory>bananaInventory=Result.SuccessIf(()=>isNewShipmentDay,newFruitInventory("banana",2),"All out of 🍌");// orboolafterBreakfast=true;Result<FruitInventory>orangeInventory=Result.FailureIf(afterBreakfast,newFruitInventory("orange",10),"No 🍊 today");Result<FruitInventory>grapefruitInventory=Result.FailureIf(()=>afterBreakfast,newFruitInventory("grapefruit",5),"No grapefruit 😢");

Implicit Conversion

Use case: Easily creating a successful result from a value

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);ResultfailedInventoryUpdate="Could not update inventory";

ToString

Use case: Printing out the state of a Result and its inner value or error

Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");ResultfailedInventoryUpdate="Could not update inventory";ResultsuccessfulInventoryUpdate=Result.Success();Console.WriteLine(appleInventory.ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.ToString());// "Failure(Could not find any bananas)"Console.WriteLine(failedInventoryUpdate.ToString());// "Failure(Could not update inventory)"Console.WriteLine(successfulInventoryUpdate.ToString());// "Success"

Map

Use case: Transforming the inner value of a successful Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex CreateMessage) passed to Result.Map() is only executed if the Result was successful

stringCreateMessage(FruitInventoryinventory){return$"There are {inventory.Count}{inventory.Name}(s)";}Result<FruitInventory>appleInventory=newFruitInventory("apple",4);Result<FruitInventory>bananaInventory=Result.Failure<FruitInventory>("Could not find any bananas");Console.WriteLine(appleInventory.Map(CreateMessage).ToString());// "Success(There are 4 apple(s))"Console.WriteLine(bananaInventory.Map(CreateMessage).ToString());// "Failure(Could not find any bananas)"

MapError

Use case: Transforming the inner error of a failed Result, without needing to check on the success/failure state of the Result

Note: the delegate (ex ErrorEnhancer) passed to Result.MapError() is only executed if the Result failed

stringErrorEnhancer(stringerrorMessage){return$"Failed operation: {errorMessage}";}Console.WriteLine(appleInventory.MapError(ErrorEnhancer).ToString());// "Success(FruitInventory { Name = apple, Count = 4 })"Console.WriteLine(bananaInventory.MapError(ErrorEnhancer).ToString());// "Failed operation: Could not find any bananas"

Testing

CSharpFunctionalExtensions.FluentAssertions

A small set of extensions to make test assertions more fluent when using CSharpFunctionalExtensions! Check out the repo for this library more information!

Includes custom assertions for

  • Maybe
  • Result
  • Result
  • Result<T, E>
  • UnitResult

Example

varresult=Result.Success(420);result.Should().Succeed();// passesresult.Should().SucceedWith(420);// passesresult.Should().SucceedWith(69);// throwsresult.Should().Fail();// throws

Analyzers

A Roslyn analyzer package that provides warnings and recommendations to prevent misuse of Result objects in CSharpFunctionalExtensions. Ensures more robust implementation when working with Result types.

Available on NuGet

dotnet add package CSharpFunctionalExtensions.Analyzers

Read or Watch more about these ideas

Related Projects

Contributors

A big thanks to the project contributors!

About

Functional extensions for C#

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages