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?