Provides functional programming utilities, which should be recognizable for rust developers also using csharp.
Installation via Package Manager Console in Visual Studio:
PM> Install-Package RSharp
Installation via .NET CLI:
> dotnet add <TARGET PROJECT> package RSharp
The Option
type is a discriminated union, which can be either Some
or None
. It is used to represent the
possibility of a value not being present.
// implicit conversion from int to Some
private static Option<int> Foo() => 2;
// implicit conversion from null to None
private static Option<int> Bar() => null;
var foo = Foo();
var result = foo.Match(
some: x => x + 1, // <= should trigger this line
none: () => 0
);
var bar = Bar();
var result2 = bar.Match(
some: x => x + 1,
none: () => 0 // <= should trigger this line
);
The Result
type is a discriminated union, which can be either Ok
or Err
. It is used to represent the possibility
of a value not being present.
private static Result<int, Exception> Divide(int a, int b) =>
b == 0
? new DivideByZeroException("Cannot divide by zero")
: a / b;
var result = Divide(4, 2);
result.Match(
ok: x => x + 1, // <= should trigger this line
err: e => 0
);
var result2 = Divide(4, 0);
result2.Match(
ok: x => x + 1,
err: e => 0 // <= should trigger this line
);
Like in Rust, the Result
type can also be unwrapped by calling the unwrap method. These methods will throw an
exception if the Result
is an Err
.
var result = Divide(4, 2);
var value = result.Unwrap(); // <= should be 2
var result2 = Divide(4, 0);
var value2 = result2.Unwrap(); // <= should throw an exception
There are also methods to unwrap the Result
type, but provide a default value if the Result
is an Err
.
var result = Divide(4, 2);
var value = result.UnwrapOr(0); // <= should be 2
var result2 = Divide(4, 0);
var value2 = result2.UnwrapOr(0); // <= should be 0
The 'Map' function is used to map a single or multiple values to a new value. It is similar to the Select
method in
LINQ.
To map
SourceObject
toTargetObject
, we use the following function for the examples below.// Define a factory method to create a new instance of the target object. private static readonly Func<SourceObject, TargetObject> Factory = source => new TargetObject(new Guid(source.Id.ToString().PadLeft(32, '0')), source.Name, source.Description, source.Value);
Note that the Map
function returns a Result type, which can be either Ok
or Err
. If the mapping fails, the Err
type will contain the exception that was thrown while mapping.'
To get the result, you can either call the Unwrap
method, or the UnwrapOr
method, or any other of the Unwrap
methods.
var a = new SourceObject(1, "Object 1", "This is the first element in the list", 1.0);
var mapResult = a.Map(Factory);
// Use the Match method to get the result, or the Unwrap method to get the result directly.
mapResult.Match(
ok: b => b, // will be triggered if the mapping succeeds
err: e => null // will be triggered if the mapping fails
);
You can also map a collection of values to a new value. Since the map function can either fail or succeed, the result
will be a collection of Result
types.
You can use the Unwrap
method to get the results that succeeded, or the UnwrapOr
method to get the results that
succeeded, or a default value if the mapping failed.
Or just use the Where
method to filter out the results that failed.
// Define the source and target objects.
internal record SourceObject(int Id, string Name, string Description, double Value);
internal record TargetObject(Guid Id, string Name, string Description, double Value);
var a = new List<SourceObject>
{
new(-1, "Object 1", "This is the first element in the list", 1.0),
new(2, "Object 2", "This is the second element in the list", 2.0),
new(3, "Object 3", "This is the third element in the list", 3.0)
};
var b = a.Map(Factory).ToList();
// Filter out the errors, should be one item in this case as the first item has an invalid id.
var errors = b.Where(x => x.IsErr()).ToList();
// Get the results that succeeded, should be two items in this case.
var results = b.Where(x => x.IsOk()).Select(x => x.Unwrap()).ToList();
The ForEach
function is used to iterate over a collection of values. It is similar to the ForEach
method in LINQ but
adds the index of the current item to the callback function.
var a = new List<SourceObject>
{
new(1, "Object 1", "This is the first element in the list", 1.0),
new(2, "Object 2", "This is the second element in the list", 2.0),
new(3, "Object 3", "This is the third element in the list", 3.0)
};
// Use the ForEach function to iterate over the collection and print the values
// to the console along with the index.
a.ForEach((item, index) => Console.WriteLine($"Item {index}: {item}"));
You can also use the ForEach function to generate a new collection of values.
var a = new List<SourceObject>
{
new(1, "Object 1", "This is the first element in the list", 1.0),
new(2, "Object 2", "This is the second element in the list", 2.0),
new(3, "Object 3", "This is the third element in the list", 3.0)
};
var b = a.ForEach((item, index) => Factory(item)).ToList();
Assert.Equal(3, b.Count);
// Use the Assert.All method to check if all items in the collection are of the correct type.
Assert.All(b, x => Assert.IsType<TargetObject>(x));