FluentSharp 1.0.7

There is a newer version of this package available.
See the version list below for details.
dotnet add package FluentSharp --version 1.0.7
                    
NuGet\Install-Package FluentSharp -Version 1.0.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FluentSharp" Version="1.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FluentSharp" Version="1.0.7" />
                    
Directory.Packages.props
<PackageReference Include="FluentSharp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FluentSharp --version 1.0.7
                    
#r "nuget: FluentSharp, 1.0.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FluentSharp@1.0.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FluentSharp&version=1.0.7
                    
Install as a Cake Addin
#tool nuget:?package=FluentSharp&version=1.0.7
                    
Install as a Cake Tool

FluentSharp

CI NuGet License: MIT

FluentSharp adds lightweight, fluent extensions to everyday C# code. Compose conditional actions, collection operations, LINQ comparisons, data reader mapping, and chains of responsibility using familiar .NET types and delegates.

The library aims to stay small and build on existing .NET functionality. It supports a functional style of composition, while allowing ordinary actions and mutable objects.

Install

dotnet add package FluentSharp

The NuGet package is named FluentSharp. Its assembly and namespaces retain the original FunctionalSharp name for compatibility with existing code.

Compatibility

The library targets .NET Standard 2.0 and has no third-party runtime package dependencies. It can be consumed by compatible .NET implementations, including .NET 10 and .NET Framework 4.7.2 or later. See Microsoft's .NET Standard compatibility guidance.

Building this repository requires the .NET 10 SDK because the test project targets .NET 10. CI runs the tests on Windows and Linux; other compatible runtimes are not currently tested by CI.

Quick start

using System;
using System.Linq;
using FunctionalSharp.Collections;
using FunctionalSharp.Validators;

var values = new[] { 1, 2, 3, 4, 5 };

values
    .Where(value => value % 2 == 0)
    .Then(evens => Console.WriteLine($"Total: {evens.Sum()}"))
    .ForEvery(value => Console.WriteLine(value));

(values.Length > 0)
    .IfTrue()
    .Then(() => Console.WriteLine("Values are available."));

API guide

Namespaces

Namespace Functionality
FunctionalSharp.Validators IfTrue, IfFalse, and IfNull
FunctionalSharp.Collections Collection actions, transformations, and iteration
FunctionalSharp.Linq LINQ overloads accepting equality delegates
FunctionalSharp.Data DbDataReader mapping
FunctionalSharp.Patterns GenericChain<T>, configuration, and custom links
FunctionalSharp.Decorators Named links using [Link]

Conditions and exceptions

IfTrue(), IfFalse(), and IfNull() return an operation object. When the condition matches, Then() executes an action and Throw() throws the supplied exception. When it does not match, subsequent operations do nothing.

using System;
using FunctionalSharp.Validators;

var count = 0;
(count < 10).IfTrue().Then(() => count++);

var isValid = false;
isValid.IfFalse().Throw(new InvalidOperationException("Invalid state."));

For argument validation:

user.IfNull().Throw(new ArgumentNullException(nameof(user)));

Then() returns the same operation object, so multiple actions can be chained. The original condition is evaluated once. Throw() is terminal.

Collections

Then() passes the entire sequence to an action and returns the original sequence. Alter() returns the sequence produced by your delegate, which may be a new collection or the original one.

using System;
using System.Linq;
using FunctionalSharp.Collections;

var values = new[] { 1, 2, 3, 4, 5 };

var result = values
    .Where(value => value < 4)
    .Then(items => Console.WriteLine(items.Count()))
    .Alter(items => items.Concat(new[] { 10 }))
    .ToList();
// result: 1, 2, 3, 10

ForEvery() executes an action for each item. For() stops at the first false condition; it does not resume at later matching items.

var values = new[] { 20, 21, 55, 77, 1 };

values.ForEvery(value => Console.WriteLine(value));
values.For(value => value < 22, value => Console.WriteLine(value)); // 20, 21
values.For((value, index) => index < 3, value => Console.WriteLine(value));

values.For(value =>
{
    Console.WriteLine(value);
    return value != 55; // Stop after processing 55.
});

For() and ForEvery() return void. These extensions do not automatically materialize a sequence; enumerating it inside Then() and again afterward can execute a deferred query twice.

Async collection wrappers

The available wrappers are ThenAsync(), AlterAsync(), ForEveryAsync(), and the three ForAsync() overloads.

await values.ForEveryAsync(value => Console.WriteLine(value));
await values.ForAsync(value => value < 22, value => Console.WriteLine(value));
await values.ForAsync((value, index) => index < 3, value => Console.WriteLine(value));
await values.ForAsync(value => value < 22);

var original = await values.ThenAsync(items => Console.WriteLine(items.Count()));
var transformed = await values.AlterAsync(items => items.Where(value => value > 20));

These methods accept synchronous delegates. ForEveryAsync() and ForAsync() run synchronous iteration through Task.Run. ThenAsync() and AlterAsync() execute their delegate synchronously and wrap the result in a task. They do not support awaiting asynchronous callbacks; passing an async lambda to an Action parameter creates async void work that the wrapper cannot await.

LINQ equality comparisons

Use equality delegates with Intersect, Except, Contains, Distinct, GroupBy, GroupJoin, Join, ToDictionary, ToLookup, and Union.

using FunctionalSharp.Linq;

var first = new[] { 1, 2, 3, 4 };
var second = new[] { 3, 4, 5 };

var shared = first.Intersect(second, (left, right) => left == right); // 3, 4
var remaining = first.Except(second, (left, right) => left == right); // 1, 2
var containsThree = first.Contains(3, (left, right) => left == right);

The delegate must describe equality. Hash-based LINQ operations also require equal values to have equal hash codes. Overloads without a hash delegate use the object's existing GetHashCode() implementation. When comparing objects by a property, the Distinct overload with a hash delegate lets you supply both:

var uniqueUsers = users.Distinct(
    (left, right) => left.Id == right.Id,
    user => user.Id.GetHashCode());

That overload currently requires a type with a public parameterless constructor. For other hash-based operations, use types whose GetHashCode() agrees with the equality delegate, or an appropriate standard LINQ comparer.

Data readers

ToList<T>() maps rows from the current DbDataReader result set to public properties with matching column names. The destination type needs a public parameterless constructor.

using FunctionalSharp.Data;

// For a reader containing columns Id, Name, and Age:
var users = reader.ToList<User>();

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

Pass ignoreCase: true to match column and property names without case sensitivity:

var users = reader.ToList<User>(ignoreCase: true);

ToMany maps two, three, or four consecutive result sets into a tuple of lists:

var (users, companies) = reader.ToMany<User, Company>();

Missing result sets produce empty lists. The caller owns and disposes the reader. Mapping uses reflection and Convert.ChangeType; columns must match writable properties and contain convertible values. There is no special handling for DBNull, nullable property types, or unmatched columns.

Chains of responsibility

GenericChain<T> executes links in order, passing a shared DataCargo<T> containing the payload and a cancellation flag.

using System;
using FunctionalSharp.Patterns;

GenericChain<int>.Create(0)
    .AddLink(data => data.Payload += 10)
    .AddLink(data => data.Payload *= 2)
    .OnError((payload, exception) => Console.WriteLine(exception.Message))
    .OnCompleted(payload => Console.WriteLine(payload)) // 20
    .Run();

Create() uses a default value-type payload or constructs a reference type with an accessible parameterless constructor. Pass an existing payload to Create(payload) to retain that instance. For strings, supply a value such as string.Empty.

Set data.Cancel = true in a link to stop processing. Cancellation also suppresses OnCompleted(). By default, an exception stops the chain and calls OnError() if a handler is registered; caught link exceptions are not automatically rethrown.

var chain = GenericChain<int>.Create(0,
    new Configuration(stopOnFailure: false, repeatTimesOnFailure: 3));

In the current implementation, this configuration allows up to three total attempts per failing link, then continues. Retries run only when stopOnFailure is false, and OnError() runs for each failed attempt. A zero repeat count currently stops on a failure even when stopOnFailure is false.

Custom links can inherit from LinkBase<T>:

public class Increment : LinkBase<int>
{
    public override void OnExecute(DataCargo<int> data)
    {
        data.Payload++;
    }
}

// Add the link with chain.AddLink(new Increment()).

To discover a link by name, decorate a concrete link class with [Link("Increment")] from FunctionalSharp.Decorators, then call .AddDecoratedLink("Increment"). Discovery scans loaded assemblies; use unique names and constructible link types matching the chain's payload type.

Build and test

Install the .NET 10 SDK, clone the repository, and run these commands from its root:

dotnet restore FunctionalSharp/FunctionalSharp.sln
dotnet build FunctionalSharp/FunctionalSharp.sln --configuration Release --no-restore
dotnet test FunctionalSharpTests/FunctionalSharpTests.csproj --configuration Release --no-build --no-restore --logger "trx;LogFileName=tests.trx" --collect:"XPlat Code Coverage" --results-directory artifacts/test-results
dotnet pack FunctionalSharp/FunctionalSharp.csproj --configuration Release --no-build --no-restore --output artifacts/packages

Test results and Cobertura coverage reports are written under artifacts/test-results. The .nupkg and .snupkg files are written under artifacts/packages. Package creation is an explicit step; building alone does not create a package.

Pushes to master run the same checks on Windows and Linux through GitHub Actions. Results and packages are available as workflow artifacts.

Maintenance and releases

FluentSharp is maintained solely by Matías Iacono. Development takes place directly on master; external pull requests are not accepted.

See the changelog for changes and the release guide for the maintainer's NuGet setup and release process. Publishing a versioned GitHub Release starts the verified NuGet publishing workflow.

License

FluentSharp is licensed under the MIT license.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 85 9/19/2026
1.0.7 89 9/19/2026
1.0.6 593 6/16/2021
1.0.5 464 5/20/2021
1.0.4 586 8/20/2020
1.0.3 633 8/15/2020
1.0.2 669 8/5/2020
1.0.1 568 8/4/2020
1.0.0 624 7/28/2020