Search code examples
c#genericsextension-methodsfunc

Extension Method Returning <T> Instead of Result<T>


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?


Solution

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