Obviously this can be done with the new tuple syntax, but in general I totally agree with everything you've said about 'out'. It is one of the ugliest language features ever, sometimes I wish MS would just say "To hell with backwards compatibility", and throw 'out' on the fire.
// Function definition
(int x, int y) GetCoordinates() =>
(100, 200);
// Invocation
var (x, y) = GetCoordinates();[1] https://github.com/louthy/language-ext/blob/master/LanguageE...
[2] https://github.com/louthy/language-ext/blob/daa5a10028e9637b...
Yes, like the standard library's Dictionary.TryGetValue()...
if (dict.ContainsKey(key)) return dict[key] else return null;
the only place where you can't avoid them are TryParse. And there it sort of makes sense - the value is sort of side effect
Which isn't thread-safe. So it's not always a better option.
var (first, middle, last) = LookupName(id1);
Seems like what you're suggesting, no?Based on my hazy memory of C#, if id is a struct/value type it is copied, but if it is a class type, there's nothing preventing the method call from mutating it.
Which is unfortunate, I agree.
There is, the class could be immutable.
public class Vector
{
public readonly int X;
public readonly int Y;
public Vector(int x, int y)
{
X = x;
Y = y;
}
public Vector With(int? X = null, int? Y = null) =>
new Vector(X ?? this.X, Y ?? this.Y);
}
I believe record-types are proposed for the next release of C#.On a similar note, I also really miss the ability to define const class methods (i.e. instance methods which do not modify the instance on which they are invoked). Although, perhaps get-only accessors fill part of that niche in C#.
But people should stop coding like this. For the love of all that is good and binary, don't use arguments to mutate values in the same scope. Just return them and reassign them. That way, state flow is always in via the arguments and out via the return value(s). Without this discipline, you will have a hodgepodge of "value flows" and that complexity will kill you when debugging. When new values can be spit out both by function returns and function arguments... Hold on, I have to take an aspirin
I know that a lot of OO/procedural languages permit you to do this (unfortunately). Don't!
This does not invalidate OP's point that it is terrible design. You do not need to take every bad idea in older languages in the name of familiarity.
This is pretty much a terrible language design idea. (Not the "out int x" change, but the whole idea of "out" in an argument list to begin with to indicate values coming out via the arguments.)
The path out of mutable argument bug-hell is not to declare that the values of some argument variables will intentionally get changed by the function in the scope where it is called. It is to make arguments immutable (like they are in the traditional mathy definition of "function"), period, and allow multiple assignment to the function call. What I mean is that, conceptually, ideally, values and state should only flow into the function arguments and only back out of the function call itself.
So for example instead of
> p.GetCoordinates(out int x, out int y);
I think the following would lead to reduced cognitive load and thus fewer bugs:
> int x, int y = p.GetCoordinates;
or even better (color me biased by Erlang/Elixir):
> {x, y} = p.GetCoordinates; # as a pattern match
Instead of a smoothly mentally-traceable flow of values into function arguments and out of function returns, we have this monstrosity of conflating mutable arguments (where values go in and then come back out changed in the scope of the function call) with this "out int x" business which seems to be indicating it intends to "push values upstream", as it were.