vkhorikov / CSharpFunctionalExtensions

Functional extensions for C#

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ensure returns Success instead of Failure

yarecky1 opened this issue · comments

Hi,

I spent a long time trying to figure it out why my method doesn't work as expected.

This works as expected resulting in x is false. Ensure returns Failure<bool>.

var r1 = Result.Success(false)
    .Ensure(x => x == true, "x is false")
    .Tap(x => Console.WriteLine("x is true"))
    .TapError(error => Console.WriteLine(error));

But this doesn't work resulting in x is true which is very confusing. Ensure returns Success<bool>.

var r2 = Result.Success(false)
    .Ensure(x => x == true) // <- no string error
    .Tap(x => Console.WriteLine("x is true"))
    .TapError(error => Console.WriteLine(error));

The only difference is in 2nd line where Ensure doesn't have error string parameter. This is confusing to everybody who doesn't expect this behaviour. Could you take a look and secure it if possible? Let me know what you think, please.

Thanks
Jarek

Hi, there'are two overloads

Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, string errorMessage) // used in the first example

Result<T> Ensure<T>(this Result<T> result, Func<T, Result<T>> predicate) // used in the second example

As you may see, the first overload accepts Func<T, bool>, while the second overload accepts Func<T, Result<T>>. So the C# compiler silently translates x => x == true into x => (Result<bool>)(x == true) because an implicit cast is defined from T to Result<T> in a way that T t is translated into Result.Success<T>(t), so you return a success Result<bool> from the "predicate" (parameter name is really horrible, because predicates can only return bool) and technically the behavior is correct.

I didn't believe that such a mature library can have a bug, so I read the library. I analysed both methods after - by pure accident - I decided to write 2nd argument as an error string. This doesn't change the feeling that this "bug" (my bug) is very hard to find. And I presume that it is very hard to find for anybody who is not very familiar with the library and use the overload in a wrong way only because it allows for it - like me, who didn't take care about error string, only about the result.

Another similar example is this below, with error string, but with the same result, it returns a Success instead of Failure.

var r1 = Result.Success(false)
    .Ensure(x => x = true, "x is false") // = instead of ==
    .Tap(x => Console.WriteLine("x is true"))
    .TapError(error => Console.WriteLine(error));

You don't want to know how long it took me to find this missing =.

I stronly recommend you to do two things

  1. Do not do x == true and x == false. Use just x and !x instead
  2. Add some analyzers (like SonarAnalyzer.CSharp which has this diagnostic that will be reported for x = true code) and use <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

Regarding the issue you've mentiones I'd say that it's not a bug in the library itself. And since this (probably confusing) behavior occurs only when typeof(T) == typeof(bool) I think it's very niche and should not be fixed in any way.

1. Do not do `x == true` and `x == false`. Use just `x` and `!x` instead

I know, it's lame, I did it during searching solution to quickly see statement and error message. I tried many different options and left this statement causing more problems to me.

2. Add some analysers (like `SonarAnalyzer.CSharp`... 

Will do, thanks.

...<TreatWarningsAsErrors>true</TreatWarningsAsErrors>`

Temporary switched off. I'm switching ancient project from VB.NET to c#, have 800+ warnings and messages after conversion, converter applies == true or == false everywhere. Right now I'm just testing different libraries.

Thank you for help and explanations!