This is a common way to handle a procedure call that can return both failure or success and a value. As noted in the comments, it is fairly common when calling APIs. Alternatives include: a) combining the return value with the success/failure so it has to be known and checked externally in the calling code, b) using an out parameter to return the desired value on success, or c) throwing exceptions to indicate failure, which is expensive and silly, so I will not discuss further.
a) Combining the return value with Success/Failure
This requires the calling code to know what represents failure in the return value instead of being explicitly told it failed.
public string GetSomeString(int id)
{
//return a value on success or null on failure
}
So the calling code has to know that a null or empty string is failure and check accordingly...
var result = obj.GetSomeString(2);
if(string.IsNullOrEmpty(result))
{
//ooops, failed
}
And this, of course, isn't an option if null is a legitimate return value.
Similarly for an int call...
public int GetSomeInt(string someArg, bool someOtherArg)
{
//return a value or a -1 for failure
}
So the calling code has to know again what is bad and assume everything else it OK...
var result = obj.GetSomeInt("blah", true);
if(result == -1)
{
//ooops, failed
}
And again, doesn't work if your "error" value is legitimate in some cases (or worse becomes legitimate at a later time).
b) Using an out parameter to pass back a value on success
An other option is to return success or failure from the method and use an out parameter to return the value if successful.
public bool GetSomeString(int id, out string someString)
{
//if fail return false
//otherwise set someString = value and return true
}
So the calling code looks like this...
string goodString = null;
if(!obj.GetSomeString(2, out goodString))
{
//ooops, something bad happened
}
This offers the advantage of separating the success/failure of a call from the value it returns. It works, but it's a klutzy syntax and if you need to return more than value, you'll end up adding more out parameters or creating an object to return in the out anyway. It also can't tell you why it failed. Which brings us back to you to the subject of your question...
Using a Result object
This gives you the advantage of b) in that success/failure is unambiguously evident and does not require the calling code to have any knowledge of what return value might indicate failure. It feels cleaner because it doesn't require using the out syntax. It also provides the additional benefit of allowing you to indicate the reason for failure by passing it back along in the message property of the result object.