A functional wrapper type for the classic try/catch statement, inspired by its counterpart in the Scala programming language (http://www.scala-lang.org/).
Also available on NuGet (http://www.nuget.org/packages/NiceTry/).
Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html). Uses the System.Reactive.Unit type made available by the guys working on the awesome Reactive Extensions (https://github.com/Reactive-Extensions) to represent the absence of a return value.
Reading the content specified by a url as string and printing it to the console. If the user specifies an invalid url, a fallback url will be used. If an error occurs anywhere during the process, the error message will be printed to the console.
Try.To(() => Console.ReadLine())
.Map(input => new Uri(input))
.OrElse(new Uri("http://google.com"))
.Using(() => new WebClient(),
(wc, url) => wc.DownloadString(url))
.Filter(content => string.IsNullOrWhiteSpace(content) == false)
.Match(content => Console.WriteLine("Success: {0}", content),
error => Console.WriteLine("Failure: {0}", error.Message));
A buffed Try
that retries the given work for up to the amount of given times:
Retry.To(() => Console.ReadLine(), 3)
.Map(input => new Uri(input))
.OrElse(new Uri("http://google.com"))
.Using(() => new WebClient(),
(wc, url) => wc.DownloadString(url))
.Filter(content => string.IsNullOrWhiteSpace(content) == false)
.Match(content => Console.WriteLine("Success: {0}", content),
error => Console.WriteLine("Failure: {0}", error.Message));
The above code sample would try reading a url from the command line up to 4 times (the original try and up to 3 retries). It returns a Success
if the user specifies a valid url before all retries are used up. If the user does not specify a valid url the fourth time, the Retry
will fail.
Try
is a wrapper around a simple try/catch statement. It tries to execute the given Action
or Func
and returns a Success
if no exception was thrown and a Failure
containing the encountered exception otherwise.
Try<int> result = Try.To(() => 2 + 4);
The above example would evaluate the anonymous function () => 2 + 4
and, since no exception will be thrown, return a Success<int>
and store it in the variable result
. The result of the calculation is stored inside the Success
and can be accessed using the Value
property:
int six = result.Value;
// or
result.OnSuccess(i => _six = i);
To find out if result
represents a Success
or Failure
it provides two boolean properties: IsSuccess
and IsFailure
. To be safe, you can either check those properties first or use the corresponding applicator.
If the above example would have thrown an exception, this could be accessed by the Error
property or the OnFailure
applicator:
Exception error = result.Error;
// or
result.OnFailure(e => _error = e);
When you use Try
or Retry
to do an Action
(which returns nothing) you'll get a Try<Unit>
as result. The Unit type represents void (read more: http://en.wikipedia.org/wiki/Unit_type) and has been implemented to avoid the unnecessary duplication of combinator and applicator functions.
Every method that could throw an exception should return a Try<T>
instead of void
or the result directly. That way, eventual exceptions can be handled by applying combinators and applicators to the return value and exception handling can be managed hassle free.
So instead of this:
private void DoSomethingRisky(string a) { //... };
private int CalculateSomethingRisky(int a, int b) { //... };
write this:
private Try<Unit> DoSomethingRisky(string a) { //... };
private Try<int> CalculateSomethingRisky(int a, int b) { //... };
Using a Try<T>
as return value states the risky nature of the method much clearer than a XML doc with an <exception>
element.
There are several ways to create a Try
.
Try<int> @try = Try.To(() => 2 + 3);
Executes an Action
or a Func<T>
synchronously and returns a Success
if no exception was thrown or a Failure
otherwise.
Try<int> five = Try.Of(5);
// same as
Try<int> five = Try.Success(5);
Create a Success
that contains the given value.
Try<int> five = Try.Failure(new Exception());
Creates a Failure
that contains the given Error.
Try<string> result = Try.Using(() => new StreamReader(File.OpenRead("some.file")), reader => reader.ReadToEnd());
Simplifies working with disposables. The first function creates a disposable, the second one allows to use it.
In the example above, result
would be a Success<string>
containing the file content as text or a Failure
if an error would have occurred.
Try<string> result = Try.Using(() => new StreamReader(File.OpenRead("some.file")), reader => Try.To(() => reader.ReadToEnd()));
Simplifies working with disposables. The first function creates a disposable, the second one allows to use it.
In the example above, result
would be a Success<string>
containing the file content as text or a Failure<string>
if an error would have occurred.
As Success
and Failure
are simple data structures, a couple of extension methods are provided that make working with both types easier.
An applicator is either void or returns something different than a Success
or Failure
. Applicators can be used to execute actions with side effect. Applicators do not catch exceptions. You have to deal with those yourself.
Try.To(() => 2 + 3)
.OnSuccess(i => _five = i);
OnSuccess
is only executed if no exception was thrown and delegates the value of the Success
to its enclosed callback. In the above example _five
would be a int with Value 5
.
Try.To(() => 5 / 0)
.OnFailure(e => _error = e);
OnFailure
is only executed if an exception was thrown and delegates the exception to its enclosed callback. In the above example _error
would be a DivideByZeroException
.
Try.To(() => 2 + 3)
.Match(
i => _five = i,
e => _error = e);
Match
allows to use a pattern matching like callback registration. The first function parameter is only executed in case of a Success
and gets the value to work with. The second function parameter is registered to handle Failure
and is only executed if an exception was thrown.
string result = Try.To(() => 2 + 3)
.Match(
i => i.ToString(),
e => "");
This overload for Match
produces a value. In the above example result
would be the string "5"
.
int five = Try.To(() => 2 + 3).Get();
Get
is the most straight forward applicator. It returns the value if the result is a Success
or rethrows the exception, if one was encountered. In the above example five
would be 5
.
int five = Try.To(() => 2 + 3).GetOrElse(-1);
GetOrElse
either returns the value, if the Try
returned a Success
or the else value, if the Try
returned a Failure
. In the above example five
would be 5
. It would have been -1
if () => 2 + 3
had thrown an exception.
int five = Try.To(() => 2 + 3).GetOrDefault();
GetOrDefault
either returns the value, if the Try
returned a Success
or the value of default(T)
, if the Try
returned a Failure
. In the above example five
would be 5
. It would have been 0
which is default(int)
if () => 2 + 3
had thrown an exception.
A combinator always returns a Try
and thus lets you combine it with other combinators and allow function composition.
This library provides a growing number of combinators that empowers you to write concise and bloat-free code for error handling. Some of them, like Map
and OrElse
have already been shown in the topmost example.
Try<int> result = Try.To(() => 5 / 0).OrElse(-1);
In the above examples a DivideByZeroException
would be thrown and result
would be a Failure
. The OrElse
combinator makes it possible to return a different value in case of a Failure
. In both cases above result
would be a Success<int>
with the Value -1.
Try<int> result = Try.To(() => 5 / 0).OrElseWith(Try.Of(-1));
In the above example a DivideByZeroException
would be thrown and result
would be a Failure
. The OrElseWith
combinator makes it possible to return a different Try
in case of a Failure
. In both cases above result
would be a Success<int>
with the Value -1.
Try<Unit> result = Try.To(() => 2 + 3)
.Then(t => t.Get() + 1)
.Then(t => Print(t.Get()));
Allows chaining and conversion of multiple Try's
. If a try fails, the chain will be interrupted and return immediately with a Failure
.
Try<Unit> result = Try.To(() => 2 + 3)
.ThenWith(t => t.Map(i => i + 1))
.ThenWith(t => t.Map(i => Print(i)));
Allows chaining and conversion of multiple Try's
. If a try fails, the chain will be interrupted and return immediately with a Failure
.
Try<Unit> result = Try.To(() => 2 + 3)
.Apply(i => Print(i));
Applies an Action<T>
to the value contained in a Success<T>
. And returns the successful or failed result of this Action
. In the above example result
would be a Success<Unit>
if Print
would not throw an exception, otherwise a Failure
.
string result = Try.To(() => 2 + 3)
.Map(i => i.ToString());
Map
allows to apply a function to the value of a Success
. In the above example result
would be a Success<string>
with Value "5"
.
Try<string> result = Try.To(() => 2 + 3)
.FlatMap(i => Try.To(() => i.ToString()));
FlatMap
allows to apply a function to the value of a Success
that returns another Try
and avoid the nesting that would occur otherwise. In the above example result
would be a Success<string>
with Value "5"
. If Map
would have been used, result
would have been a Success<Success<string>>
.
Try<int> result = Try.To(() => 2 + 3)
.Filter(i => i == 5);
Filter
checks if a given predicate holds true for a Try
. In the above example result
would be a Success<int>
with Value 5
. If the predicate i => i == 5
would not hold, result
would have been a Failure
containing an ArgumentException
. If () => 2 + 3
would have thrown an exception result
would have been a Failure
containing the thrown exception.
Try<int> result = Try.To(() => 2 + 3)
.Reject(i => i == 5);
Is the exact opposite of Filter
. It checks if a given predicate does not hold true for a Try
. In the above example result
would be a Failure
containing an ArgumentException
.
Try<int> five = Try.To(() => 2 + 3);
Try<int> four = Try.To(() => 6 - 2);
Try<int> nine = five.Zip(four, (a, b) => a + b);
Takes one other Try
and allows to apply a Func
to the values of both Try
's. If one Try
would be a Failure
the function would not be applied.
In the above example nine
would be a Success<int>
containing 9
.
Try<int> five = Try.To(() => 2 + 3);
Try<int> four = Try.To(() => 6 - 2);
Try<int> nine = five.ZipWith(four, (a, b) => Try.To(() => a + b));
Takes one other Try
and allows to apply a Func
to the values of both Try
's. If one Try
would be a Failure
the function would not be applied.
In the above example nine
would be a Success<int>
containing 9
.
Try<int> result = Try.To(() => 5 / 0)
.Recover(e => -1);
This combinator is used to recover from a Failure
. Gets the thrown exception to work with. In the above example result
would be a Success<int>
with Value -1
and e
would be a DivideByZeroException
.
Try<int> result = Try.To(() => 5 / 0)
.RecoverWith(e => Try.Of(-1));
This combinator is used to recover from a Failure
with a new Try
. Gets the thrown exception to work with. In the above example result
would be a Success<int>
with Value -1
and e
would be a DivideByZeroException
.
Try<string> result = Try.To(() => 2 + 3)
.Transform(
i => i.ToString(),
e => e.Message);
Can be used to transform the result of a Try
. The first function parameter transforms the resulting value if it is a Success
, the second works on the exception if it is a Failure
. In the above example result
would be a Success<string>
with Value "5".
Try<int> result = Try.To(() => 2 + 3)
.Succeed(6);
Ignores the Try
it is called from completely and returns a new Success<T>
with a given value. In the above example result
would be a Success<int>
with value 6.
Try<int> result = Try.To(() => 2 + 3)
.Fail(new ArgumentException("I can't do this, Dave."));
Ignores the Try
it is called from completely and returns a new Failure
with a given Exception. In the above example result
would be a Failure
containing an Exception.
Try<string> result = Try.To(() => 2 + 3)
.Retry(i => i.ToString(), 2);
Allows to retry a certain step a given number of times (at least once, if no retry count is given). Works the same as Retry.To
as documented above.
Try<int> result = Try.To(() => 2 + 3)
.Tap(i => Console.WriteLine(i))
.Map(i => i + 1);
Can be used to take a look at the value of a Try
without modifying it. In the above example result
would be a Success<int>
containing the value 6. Tap
would have printed 5 to the console.
Note: If the action in Tap
throws, the exception is swallowed.
Try<string> result = Try.To(() => File.ReadAllText("some.file"))
.Finally(() => File.Delete("some.file"))
.Map(content => content.Replace("meh", "awesome"));
Can be used to execute side effecting behavior without modifying a Try
. In the above example result
would be a Success<string>
containing the read text. The given Action
will be executed in either case, regardless if the incoming Try
is a Success
or a Failure
.
Note: If the action in Finally
throws, the exception is swallowed.
Try<string> result = Try.Using(() => new StreamReader(File.OpenRead("some.file")), reader => reader.ReadToEnd());
// or
Try<string> result = Try.To(() => File.OpenRead(file))
.Using(stream => new StreamReader(stream),
reader => reader.ReadToEnd())
Simplifies working with disposables. The first function creates a disposable, the second one allows to use it.
In both examples above, result
would be a Success<string>
containing the file content as text or a Failure
if an error would have occurred.
Try<string> result = Try.Using(() => new StreamReader(File.OpenRead("some.file")), reader => Try.To(() => reader.ReadToEnd()));
// or
Try<string> result = Try.To(() => File.OpenRead(file))
.Using(stream => new StreamReader(stream),
reader => Try.To(() => reader.ReadToEnd()))
Simplifies working with disposables. The first function creates a disposable, the second one allows to use it.
In both examples above, result
would be a Success<string>
containing the file content as text or a Failure
if an error would have occurred.