Its an age old problem. You have a method that needs to report whether it executed successfully and if it did, you need to return the result.
int? SolveProblem(int x, int y)
{
var errors = new List<string>();
if (x < 0)
errors.Add("x cannot be less than zero");
if (y < 0)
errors.Add("y cannot be less than zero");
if (errors.length == 0)
return x + y;
else
//now what?
}
Some developers will choose to return null / Nothing here to represent a failure. However, this is not really useful to the caller as they will not know why. Others will choose to throw an exception, which is a little better, but exceptions have overhead and really just push the same problem up the stack. If the calling method needs to return a list of errors to its caller the exception will need to be parsed to re-constitute the messages.
Another approach is to use an output / ByRef parameter (which is what Microsoft does with things like int.TryParse)
int? SolveProblem(int x, int y, out List<string> errors)
This is the best option yet, but requires the caller to define a variable to hold the errors prior to calling, which is ok, but not ideal.
List<string> errors; //annoying
var answer = SolveProblem(x, y, out errors);
Of course we could create a "complex object" holding both the errors collection and the answer.
public class MyAnswer
{
public MyAnswer()
{
Errors = new List<string>();
}
public List<string> Errors { get; set; }
public int? Data { get; set; }
public bool Success { get { return Errors.Count == 0; } }
}
This does produce a better experience for the caller, however, it is really annoying for the method developer since it would require a new class for each type of "answer" returned. That is, unless you decide to be a little creative with generics.
public class MyAnswer<T>
{
public MyAnswer()
{
Errors = new List<string>();
}
public List<string> Errors { get; set; }
public T Data { get; set; }
public bool Success { get { return Errors.Count == 0; } }
}
Now we can use this class in all our methods, without exceptions, output parameters, or one-off classes.
MyAnswer<int?> SolveProblem(int x, int y)
{
var answer = new MyAnswer<int?>();
if (x < 0)
answer.Errors.Add("x cannot be less than zero");
if (y < 0)
answer.Errors.Add("y cannot be less than zero");
if (answer.Success)
answer.Data = x + y;
return answer;
}