A simple .net Optional type designed for productivity.
https://www.nuget.org/packages/Optional.net/
- Added extension methods to IEnumerable<Optional> to allow you to take the first N elements that are not empty
// Lazily tries 3 different functions and tries to return the first non-empty result, in this case
// it will return an IEnumerator containing "hello" and never call SomeReallyExpensiveFunction
return [
() => Optional<string>.Empty,
() => Optional.Of("hello"),
() => SomeReallyExpensiveFunction()
].TakePresent(1);
// Evaluates optional values in a collection and returns the first 2 non-empty results, in this case
// it will return an Optional containing "hello" and "world"
return [
Optional.Of("hello"),
Optional<string>.Empty,
Optional.Of("world")
].TakePresent(2);
# New in 3.6
* Added Optional.Coalesce() and Optional.First() methods for handling collections of optional values and collections-of-functions that return optional values
```c#
// Lazily tries 3 different functions and returns the first non-empty result, in this case
// it will return an Optional containing "hello" and will never call SomeReallyExpensiveFunction()
return Optional.Coalesce([
() => Optional.Of("hello"),
() => Optional<string>.Empty,
() => SomeReallyExpensiveFunction()
]);
// Evaluates optional values in a collection and returns the first non-empty result, in this case
// it will return an Optional containing "hello" and will never call SomeReallyExpensiveFunction()
return Optional.First([ Optional<string>.Empty, Optional.Of("hello"), Optional.Of("word") ]);
- .net 8 build added!
- Added IfNotPresentAsync Task extension method
- Greater test coverage
- .net 7 build added!
- Full nullable type annotations for the API
- New overloads for All(), Any() and Get() for easier unpacking of values
- Fixed a bug in UnpackPartial when using arrays
- .net 6 build added!
- Added Task variants of .OrElseAsync for nicer fallback chaining, e.g.
// Lazily tries 3 different functions and returns the first non-empty result, otherwise
// falling back to false at the end if none of them returned a value
return await TrySomethingAsync()
// If TrySomethingAsync() returns empty, execute the function chain below
// This is lazily executed, so it will never execute if TrySomethingAsync() returns something.
.OrElseAsync(() => TrySomethingElseAsync().MapAsync(somethingElse =>
{
return "fallback-value-1";
})
// If _that_ didn't work, lazily try this instead!
.OrElseAsync(async () =>
{
if (await SomethingElse())
{
return Optional.Of("fallback-value-2");
}
return Optional.Empty;
})
// Nothing worked, but we might want to provide a default value
.OrElseAsync(() =>
{
Log.Warn("nothing worked!");
return "oh no!";
});
- Added .FlatMapAsync() extensions to Task
- Added .IfPresentAsync() extensions to Task
- Added OrElseThrow() to rethrow an exception if the given
Optional
does not have a value. The original exception call stack is preserved.
// Chain async optional tasks together, the tasks only execute if the previous task returned a value
Task<Optional<long>> input = Task.FromResult(Optional.Of(1234567890L));
Optional<string> output = await input.FlatMapAsync(async x => x.ToString());
// Rethrow exceptions in a clean manner via OrElseThrow()
try
{
SomethingThatMightThrow();
}
catch (Exception ex)
{
SomeFallbackThingThatReturnsOptional().OrElseThrow(ex); // Original call stack of ex is preserved
}
// Perform some async computation that might return a result,
// map the result or fall back to another value!
string result =
await Func()
.MapAsync(async x => x + " even more string")
.OrElseAsync(() => "Fallback from async optional method chain");
async Task<Optional<string>> Func()
{
return Optional.Empty;
}
- Added extension methods on Task<Optional> to make chaining optional async methods nicer
string result = await SomeAsyncFunc().OrElseAsync(() => "hello!");
Optional<T> result = await SomeAsyncFunc().OrElseAsync(SomeOtherFunThatReturnsOptional);
Optional<string> result = await Task.FromResult("Hello").MapAsync(x => x + " World!");
Optional<string> result = await SomeAsyncFunc().MapAsync(async x => "Hello from an async Task {x}!");
- Support for .net framework 4.6.1 and 4.7.2 alongside existing .net core support
I've added support for .NET Core 3.0 and I've made Optional<T>
a readonly struct
(It was never mutable anyway), which should give some small perf wins through reduced copies on member invocations.
F#'s option type is fantastic until anything in C# needs to deal with it. So I've added some implicit conversions from FSharpOption and FSharpValueOption into their equivalent Optional.net types. Additionally I've added a .ToFsOption() method to Optional.net optionals to make them easy for F# to consume in a performant manner.
module MyFSharpModule =
let DoSomething () = 42 option // Return an F# option like normal
...
void Main()
{
Optional<int> result = MyFsharpModule.DoSomething(); // Implicitly converts F# option into C#-friendly Optional.net option
}
class MyCSharpClass
{
public static Optional<string> DoSomething() => Optional.Of(42) // Return an Optional.net option
}
let myFun () =
match MyCSharpClass.DoSomething().ToFsOption() with
| Some value -> printfn "Easily consume Optional.net from F#! %O" value
| None -> printfn "Life is better when we all get along"
Optional behaviour when returning values
void Main()
{
SomeOtherFunc().IfPresent(value => Console.WriteLine(value));
}
Optional<string> SomeOtherFunc()
{
return Optional.Of(someField).IfNotPresent(() => "fallback value");
}
Capture nulls and gracefully fall back
void Main()
{
var value = Optional.Of(NullableLibraryFunction()).OrElse("treachery!");
}
Implicitly converts values into Optionals
string SomeFunc()
{
return "test";
}
Optional<string> result = SomeFunc();
result.IfPresent(x => Console.WriteLine(x));
Automatically converts nulls to Optional.Empty
string SomeFunc()
{
return null;
}
Optional<string> foo = SomeFunc();
Console.WriteLine($"Result HasValue = {foo.HasValue}");
Chain through multiple optional functions until one returns
void Main()
{
var value = Optional.get(Func1, Func2, Func3);
}
Optional<int> Func1()
{
return Optional.Empty;
}
Optional<int> Func2()
{
return Optional.Empty;
}
Optional<int> Func3()
{
return Optional.Of(42);
}
Async friendly
async Task Main()
{
// Mix sync and async functions using Optionals
await SomeFunc().IfPresentAsync(value =>
{
await DoAsyncWork(value);
Console.WriteLine("Async!");
});
// Compose optional async tasks together!
Optional<char[]> _ = await SomeFunc().MapAsync(x =>
{
return x.ToArray();
});
}
Optional<string> SomeFunc()
{
return Optional.Of("test");
}
async Task DoAsyncWork(string value)
{
await File.WriteAllTextAsync("./file.text", value);
}
Safely transform values
void Main()
{
Func1().Map(x => x + "mapping ")
.Map(x => x + "values ")
.Map(x => new StringBuilder(x))
.Map(x => x.Append("safely!"))
.Map(x => SomeFunctionThatMightReturnNull(x))
.IfPresent(x => Console.WriteLine(x.ToString()));
}
Fallback when a method returns empty
void Main()
{
var result = MaybeSomething().OrElse(() => "Something else"))
}
Optional<string> MaybeSomething()
{
return Optional.Empty;
}
Wrap values into collections and then filter those collections to just the present values
void Main()
{
IEnumerable<Optional<string>> results = Optional.Pack(SomeLibraryFunctionThatReturnsAString(),
AnotherFunctionThatMightReturnNull(),
AnotherFunction());
// Pick all of the non-empty values from the list, discarding the empty ones
Optional.UnpackPartial(results).IfPresent(listOfOnlyPresentValues =>
{
Console.WriteLine("At least one value was present");
foreach(var val in listOfOnlyPresentValues) {
Console.WriteLine(val);
}
})
.IfNotPresent(() => Console.WriteLine("All values were empty!"))
// Create an IEnumerable<T> if ALL values in "results" are present, otherwise return empty
Optional.UnpackAll(results).IfPresent(listOfAllValues =>
{
foreach(var val in listOfAllValues) {
Console.WriteLine(val);
}
});
}