Reputation: 7553
I've started using the pattern of a generic result type as a wrapper object that includes a return value and information about the operation like whether it succeeded. Here's an example:
public class Researcher
{
public Result<string> LearnThing(bool factDesired)
{
// Validate and bail if failure
var validationResult = ValidateIntentions(factDesired);
if (!validationResult.Succeeded)
{
// Ideally: return validationResult directly without converting to new instance
return Result.Failure<string>(validationResult.Error);
}
return StateOpinion();
}
public Result<string> StateOpinion()
{
return Result.Success("I like turtles");
}
public Result<bool> ValidateIntentions(bool factDesired)
{
if (factDesired)
{
// Ideally: no <bool> required, infer default instead
return Result.Failure<bool>("Only opinions here, sorry");
}
else
{
return Result.Success(true);
}
}
}
public class Result<T>
{
public bool Succeeded { get; set; }
public string Error { get; set; }
public T Value { get; set; }
}
// Static helpers
public static class Result
{
public static Result<T> Success<T>(T value)
{
return new Result<T> { Succeeded = true, Value = value };
}
public static Result<T> Failure<T>(string error)
{
return new Result<T> { Succeeded = false, Error = error };
}
}
Here, the generic Result<T>
class is used on each method and a static helper class provides a mechanism to create the results with success status implied. So far, this is working nicely.
The one bikeshedding annoyance I have with this approach is that I need to restate the <T>
often where ideally it could be inferred or when I no longer care about T Value
(which would be default
) and only about Error
, as in the case of failures. I somewhat understand that C# doesn't infer from method return types, but I have come across some mentions of implicit operators that seem to allow some cool tricks that I don't quite understand.
So, I humbly submit the question to the C# wizards among you: is there some variation or magic I can add to this approach to achieve more type inference and effectively an implicit Result<"I don't care">
for failure results?
Upvotes: 1
Views: 508
Reputation: 172220
You can use exactly the same technique as described in the article you linked to.
Step 1: You define a non-generic helper class for your Failure case:
public class FailureResult
{
public string Error { get; }
public FailureResult(string error) { Error = error; }
}
Step 2: You change your static helper to return a FailureResult
instead of a Result<T>
:
public static class Result
{
...
public static FailureResult Failure(string error)
{
return new FailureResult(error);
}
}
Step 3: You define an implicit conversion from FailureResult
to Result<T>
:
public class Result<T>
{
...
public static implicit operator Result<T>(FailureResult result)
{
return new Result<T> { Succeeded = false, Error = result.Error };
}
}
Step 4: Profit
public Result<bool> ValidateIntentions(bool factDesired)
{
if (factDesired)
{
// No <bool> required!
return Result.Failure("Only opinions here, sorry");
}
else
{
return Result.Success(true);
}
}
(fiddle)
Upvotes: 3
Reputation: 81493
You could get around the bool
issue with just an overload:
public static Result<bool> Failure(string error)
{
return new Result<bool> { Succeeded = false, Error = error };
}
Allowing this:
return Result.Failure("Only opinions here, sorry");
As for:
// Ideally: return validationResult directly without converting to new instance
return Result.Failure<string>(validationResult.Error);
You could use an implicit operator:
public static implicit operator Result<string>(Result<T> result)
{
return Result.Failure<string>(result.Error);
}
Which would allow you to do:
if (!validationResult.Succeeded)
{
// Ideally: return validationResult directly without converting to new instance
return validationResult;
}
Though I personally wouldn't do this, it's unexpected and misusing the language feature.
You could however use an instance method or extension method:
public Result<string> AsError()
{
return Result.Failure<string>(Error);
}
In all honesty, I think what you have is declarative and not trying to be magic. I would just stick with some helper (extension) methods if need be.
Upvotes: 1
Reputation: 13527
Maybe something like this:
public class Result<T>
{
public bool Succeeded { get; set; }
public string Error { get; set; }
public T Value { get; set; }
public bool HasValue { get; protected set; } = true;
}
public class Result : Result<object>
{
public Result() { HasValue = false; }
}
public static class ResultFactory
{
public static Result<T> Success<T>(T value)
{
return new Result<T> { Succeeded = true, Value = value };
}
public static Result Success()
{
return new Result { Succeeded = true };
}
public static Result Failure(string error)
{
return new Result { Succeeded = false, Error = error };
}
}
This allows you not to have to "kludge" in a bool somewhere to to make it fit your pattern. Sure you are creating a null object, but that is more-or-less tucked away.
Upvotes: 0