I remember when I first saw the spec for C#. It was around the time of the beta release. C# was a fresh clean language. The first thing that I noticed, though, was that it had more stuff. I used to joke with friends that it was as if the language designers had taken the list of bugs for Java and decided to do something about each of them. To be fair, they did a great job but C#, even in its early days, was not a minimal language. If a feature had a legitimate use and Java decided not to include it, chances are, C# did – more power to the programmer.
There were two features that puzzled me though: ‘ref’ and ‘out’. The idea for these had been around for ages. Ada had them, and they’d cropped up in a number of Algol-derived languages. You could even argue that C had the rudiments of the semantics – you could always pass pointers into your functions and modify what they refer to, but to me, that was really more of a convenience. When you don’t have exceptions, error codes are often munged into the return value. Passing values back via parameters can be cleaner at times in C, but if you are working in a more modern language there really are better alternatives.
The thing I didn’t get at the time was why ‘ref’ and ‘out’ where there. I could understand ‘ref’ being used for interop but ‘out’ seemed archaic unless it was there for some sort of value type optimization. I didn’t think about it much beyond that. It wasn’t until a while ago that I started to think about it again after I’d seen a team that overused ‘ref’ and ‘out’ severely.
Whenever you are tempted to use ‘ref’ or ‘out’ in C# consider whether you can do the work by designing functions with single return values.
This may seem extreme, but it isn’t, really. Our thoughts become clearer when we force ourselves to reconsider our designs under that constraint. Sometimes we find that we can decompose the problem into several functions rather than one. At other times we find some relationship between outputs that leads us to create a new class or struct that we can use as a single return type. Really, a function is supposed to do some thing for you, not some things. If a function is giving you two things, it's beyond its quota.
There really are only two cases: either the things that you want the caller to know about are related (and you can show that relationship in a data structure) or they aren’t and you should consider letting the caller ask for them independently. In the first case, you are raising the level of abstraction the system by introducing a new type. In the second, you are increasing the orthogonality of the system by making sure that functions do one useful thing for the caller. Either improves design.
If you need more convincing, attempt to extract random pieces of code into methods with a refactoring tool in C#. Nearly every case where tools choose to use 'ref' and 'out' are malformed abstractions. In languages without 'ref' and 'out' the tool would just say "no can do."
End note: The trick of bundling a function’s outgoing values into a class isn’t always called for, but it is a great tool to have in your arsenal. I can’t tell you the number of times I’ve seen classes that came about that way move toward centrality in a design.