CSharpFunctionalExtensions icon indicating copy to clipboard operation
CSharpFunctionalExtensions copied to clipboard

Extension to check and map from nullable to non-nullable types

Open BernhardMaier opened this issue 4 years ago • 3 comments

Hey Vladimir,

in our current project we migrated from EF Core to EF 6 and end up with a bunch of Dereference of a possible null reference warnings. Thats because the return type of .FindAsync() changed from a non-nullable to a nullable type (object => object?). This is the initial code that worked without warnings in EF Core:

 var result = await NonEmptyId.Create(id))
    .Map(async guid => await _context.offers.FindAsync(guid))
    .Ensure(offer => offer != null, HttpStatusCode.NotFound.ToString())
    .Bind(offer => offer.Description);

After the migration to EF 6, offer.Description throws the null-reference warning although a null-check is done in the prevoius .Ensure(), because the incoming offer is still of type Offer?. I fixed the warnings by adding .Map(offer => offer!) after the .Ensure() and the offer in .Bind() will now be of type Offer:

 var result = await NonEmptyId.Create(id))
    .Map(async guid => await _context.offers.FindAsync(guid))
    .Ensure(offer => offer != null, HttpStatusCode.NotFound.ToString())
    .Map(offer => offer!)
    .Bind(offer => offer.Description);

But I was not happy with that solution, because it requires to add the .Map() at every place where we use .FindAsync() and so i end up with adding a new Result extension .EnsureHasValue() that combines/wraps the .Ensure() and .Map(). Now the code looks very similar to the initial implementation:

 var result = await NonEmptyId.Create(id))
    .Map(async guid => await _context.offers.FindAsync(guid))
    .EnsureHasValue(HttpStatusCode.NotFound.ToString())
    .Bind(offer => offer.Description);

The signatures of the extension and the overlaod for async use are

Result<T> EnsureHasValue<T>(this Result<T?> result, string error)
Task<Result<T>> EnsureHasValue<T>(this Task<Result<T?>> resultTask, string error)

My Question now is: Do you think this extension is it worth to add it to the CSharpFunctionalExtensions or is this use case not common enough?

BernhardMaier avatar Jan 31 '22 09:01 BernhardMaier

It is a good extension, but I still need to figure out the integration of non-nullable reference types with Maybe that's part of the library ( https://github.com/vkhorikov/CSharpFunctionalExtensions/issues/341 ), so we need to hold off on that.

BTW, have you tried the Maybe<T> class from the library? If so, what's your opinion in terms of usability vs T? ?

vkhorikov avatar Feb 06 '22 12:02 vkhorikov

Like you said, there is currently no easy way to map a nullabale object to an equivalent Maybe, for example Offer? to Maybe<Offer> via Maybe<T>.Create(T? obj).

I implemented a method to test how it would feel to work with Maybe instead of Result, but i end up with more code either to handle Maybe.None or with mapping it to Result with .ToResult() to process futher. The example i gave you in the issue is strongly simplified and in the real code the mapping to Result is nearly unavoidable.

Currently i work on getting more confident with Maybe and to know more of the extensions it provides. So, maybe i will find some other good ways to use it in my code.

BernhardMaier avatar Feb 07 '22 08:02 BernhardMaier

Thanks for your input. If you have any further concerns/use cases/suggestions, feel free to share here, I'll take them into account when I start working on that feature.

vkhorikov avatar Feb 08 '22 16:02 vkhorikov

Closed with #457

BernhardMaier avatar Feb 07 '23 11:02 BernhardMaier