I'm studying Vladimir Khorikov's Result
class and how it can be used to chain Result operations.
Their original article can be found here.
Their original Result
class code can be found here.
My edited Result
class codes are as follows:
public class Result
{
private bool _isSuccess;
private string _errorMsg = "";
public bool IsSuccess()
{
return _isSuccess;
}
public bool IsFailure()
{
return !_isSuccess;
}
public string ErrorMsg()
{
return _errorMsg;
}
public Result(bool isSuccess, string errorMsg)
{
bool errorMsgIsEmpty = string.IsNullOrEmpty(errorMsg);
if (isSuccess && !errorMsgIsEmpty)
{
throw new Exception("cannot have error message for successful result");
}
else if (!isSuccess && errorMsgIsEmpty)
{
throw new Exception("must have error message for unsuccessful result");
}
_isSuccess = isSuccess;
if (!errorMsgIsEmpty)
{
_errorMsg = errorMsg;
}
}
public static Result Fail(string errorMsg)
{
return new Result(false, errorMsg);
}
public static Result<T> Fail<T>(string errorMsg)
{
return new Result<T>(default(T), false, errorMsg);
}
public static Result OK()
{
return new Result(true, "");
}
public static Result<T> OK<T>(T value)
{
return new Result<T>(value, true, "");
}
public static Result Combine(params Result[] results)
{
foreach (Result result in results)
{
if (result.IsFailure())
{
return result;
}
}
return OK();
}
}
public class Result<T> : Result
{
private T _value;
public T Value()
{
return _value;
}
public Result(T value, bool isSuccess, string errorMsg) : base(isSuccess, errorMsg)
{
_value = value;
}
}
I am working with the following test class:
public class Fruit
{
private string _name = "";
private StringBuilder _attribs;
public bool isBad;
public Fruit(string name)
{
_name = name;
_attribs = new StringBuilder();
}
public string Name()
{
return _name;
}
public string Attribs()
{
string attribs = _attribs.ToString();
if (attribs.Length > 0)
{
return attribs.Remove(attribs.Length - 2);
}
return attribs;
}
public void AddAttrib(string attrib)
{
_attribs.Append(attrib + ", ");
}
}
Below is the class that operates on Fruit
:
public class FruitOperator
{
public static Result<Fruit> AddAttribToFruit(Fruit fruit, string attrib, bool fail)
{
if (fail)
{
return Result.Fail<Fruit>("failed");
}
fruit.AddAttrib(attrib);
return Result.OK<Fruit>(fruit);
}
public static void MarkFruitAsBad(Fruit fruit)
{
fruit.isBad = true;
}
}
I have created the following Result
extension methods to match the function signatures of AddAttribToFruit
and MarkFruitAsBad
:
public static class ResultExtensions
{
public static Result<T> OnSuccess<T>(this Result<T> result, Func<T, string, bool, Result<T>> func, T val, string str, bool flag)
{
if (result.IsFailure())
{
return result;
}
return func(val, str, flag);
}
public static Result<T> OnFailure<T>(this Result<T> result, Action<T> action)
{
if (result.IsFailure())
{
action(result.Value());
}
return result;
}
}
My problem is when I try to use the result of OnSuccess
in the next operation:
Fruit fruit = new Fruit("apple");
Result<Fruit> fruitResult = FruitOperator.AddAttribToFruit(fruit, "big", false)
.OnSuccess(FruitOperator.AddAttribToFruit, fruit, "red", true)
.OnFailure(lastFruitResult => FruitOperator.MarkFruitAsBad(lastFruitResult.Value()));
Above, lastFruitResult
is actually a Fruit
instead my expected Result<Fruit>
.
Is there something wrong with my extension method signatures, or is there something that I need to change with how I'm using them?
Your signature in OnFailure
is slightly wrong. Change it to OnFailure<T>(this Result<T> result, Action<Result<T>> action)
.
Basically an Action
is a delegate that takes a number of parameters. The difference from Func
is that Action
doesn't return a value.
OnFailure<T>(this Result<T> result, Action<T> action)
will let the consumer pass an action with the type T
as input. In your case T
is Fruit
since it is actually defined in AddAttribToFruit
because it returns Result<Fruit>
.
By changing the signature to:
OnFailure<T>(this Result<T> result, Action<Result<T>> action)
it will let the consumer create an action with the type Result<T>
, which in your case is Result<Fruit>
.
Your OnFailure
should probably look something like this:
public static Result<T> OnFailure<T>(this Result<T> result, Action<Result<T>> action)
{
if (result.IsFailure())
{
action(result); // Note that result is Result<T> and action takes Result<T> as parameter
}
return result;
}
lastFruitResult
will be the type definied within Action<here>.