1

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?

Floating Sunfish
  • 4,920
  • 5
  • 29
  • 48
  • Take a close look at `Action` in `OnFailure(this Result result, Action action)` – ProgrammingLlama Jan 17 '20 at 06:47
  • 1
    From the first look... change `Action action` -> `Action> action` in `OnFailure` – smoksnes Jan 17 '20 at 06:48
  • 1
    @smoksnes Hey, it actually worked! Could you add this as an answer so I can accept it? Also, could you add an explanation as to how changing `OnFailure`'s signature affects what I get from `OnSuccess`? – Floating Sunfish Jan 17 '20 at 06:50
  • @Floating You don't get `lastFruitResult` from `OnSuccess`. – ProgrammingLlama Jan 17 '20 at 06:51
  • @John Oh? Where does it come from then? – Floating Sunfish Jan 17 '20 at 06:52
  • `action(result.Value());`. [Try this code](https://rextester.com/YATAN59912) to understand it: `Action DoWrite = (value) => Console.WriteLine(value); DoWrite("hello");` – ProgrammingLlama Jan 17 '20 at 06:53
  • @John So what `Action` needs dictates what I get from `identifier =>`? Could you link some documentation or article that explains why this happens? – Floating Sunfish Jan 17 '20 at 06:56
  • I think [this answer](https://stackoverflow.com/a/3282205/3181933) might help. If you've seen something like `list.FirstOrDefault(item => item.Name == "TestUser");` then it's the same thing as you have here (except that's usually a `Predicate` delegate). – ProgrammingLlama Jan 17 '20 at 06:57
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206124/discussion-between-floating-sunfish-and-john). – Floating Sunfish Jan 17 '20 at 07:01

1 Answers1

2

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>.

smoksnes
  • 10,509
  • 4
  • 49
  • 74
  • Just wanted to add the final conclusion here. `Func` and `Action` determines the value (and data type) it gets from the object being extended when using `someIdentifer =>`. – Floating Sunfish Jan 17 '20 at 07:47