roslyn: Allow ref-returning get-only auto-properties
So, currently in C# 7.0 we have old boring auto-properties with set accessors:
public class OldSchoolPerson {
public string Name { get; set; }
public int Age { get; set; }
}
And a new efficient ref-returning get-only (we all hate setters!) modern property pattern:
public class ModernPerson {
private string _name;
private int _age;
public ref string Name => ref _name;
public ref int Age => ref _age;
}
New modern property pattern has improved performance of compound operators:
var person = new ModernPerson { ... };
person.Age ++; // no setter call!!
And more importantly: ability to pass modern properties as an argument to ref
parameter (we all waited this for years!!):
void M(OldSchoolPerson oldSchool, ModernPerson modern) {
ModernStringMutator(ref oldSchool.Name); // error :((
ModernStringMutator(ref modern.Name); // works!!!!
}
void ModernStringMutator(ref string text) => text += "ref returns rules!!!";
With all that benefits in mind, I don’t see a single reason to use old-school properties again! The only problem is the verbose syntax of modern property pattern, requiring backing field declaration. Using ref
modifier with get-only auto-property syntax produces error for some reason:
public class ModernPerson {
// CS8145: Auto-implemented properties cannot return by reference :((
public ref string Name { get; }
// // CS8145: Auto-implemented properties cannot return by reference :((((((
public ref int Age { get; }
}
Dear Roslyn team, please allow get-only auto-properties to return by reference to allow concise way to express modern property pattern.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 8
- Comments: 65 (27 by maintainers)
I hope this is some kind of joke.
ref
returns are great for certain things but what you are suggesting is pure nonsense. You may as well make the fields public.For example:
It’s actually worse:
AddRange
will likely result in array resizing and nowx
points to element 0 of the old array. Good luck modifying the element 0 of thelist
viax
.I was simply pointing out that it was a core design goal to enable mutation with refs. That was a primary scenario that customers need for the problems we were trying to address.
So stating that it’s a ‘misuse’ to allow for mutation of ref-returns does not actually fit with our design goals for the feature. A core intent is to allow cheap mutation of large value types. We would certainly like features like ‘readonly-ref’ in the future. But in the short term a primary goal was precisely to enable exactly this behavior.
@jnm2,
You are probably right. I like to write that way, but I probably need to tone it down a bit at times (or maybe tone it down a lot, a lot of the time).
@CyrusNajmabadi,
In the current “let’s sell out the language to the highest bigger” world of the C# language team, you are free to take that view if you want. And with your “7% sounds absolutely massive to me” attitude, the language team will no doubt help to lower the quality of the language further by encouraging premature/micro optimisation behaviour, such as the inappropriate use of
ref
.In my world though (ironically stemming from some excellent teaching from game developers), mutation of values is always seen as a negative, except in edge cases. Clearly game engines are one such edge case and ref locals/returns are a huge quality win here as it removes the need for globally accessible state to avoid slow value copying. They are edge cases though and, as a norm, using
ref
to avoid copying values, then mutating those original values is a misuse of ref returns.@CyrusNajmabadi
Then what is the point of
readonly
modifier? Would it make sense to say that if you don’t want to mutate fields, then don’t mutate them? I think that’s part of the compiler’s job to prevent it if it’s not desirable.@DavidArno who do you think this ‘highest bidder’ was?
In what way do you think the language has “sold out”? What does it even mean in the context of this discussion, or in the development of C#? Did we ever claim some cause that we would be somehow loyal to? Did we make claims about things we would/wouldn’t do in the language?
I’ve been working on this language for a large portion of my life. And the only driving desire i’ve ever had (and i’ve ever seen from the team), is to make a useful language for a broad spectrum of developers working on an enormously diverse set of tasks.
Indeed, if it hasn’t been clear by version 7, C# has been happy to adopt and adapt to all sorts of patterns and practices across the developer ecosystem. We don’t claim any sort of ideological purity of our language. We’re not pure OO, but we have aspects of OO systems in us. We’re not functional, but we have aspects of functional languages in us. We’re not dynamic, but we have embraced some dynamic features. We’re not focused on low level programming, but we take in features to make that easier at times. We’re not primarily concerned with any particular platform or domain. Want to use us for mobile? Great. For the server? Great. On the desktop? Great. On a TV? Go for it. Want to use us on MS’ stack, please do. On someone else’s stack? Be our guest. Use us far and wide. etc. etc.
We want our language to be useful to an incredibly broad spectrum of developers. Part of that spectrum includes people who are trying to squeeze every last drop of performance in heavily constrained scenarios. With the help of them, and others, we identified a very cheap and simple feature change to the language that helped them out enormously. These customers got huge wins, with just a small amount of work from us (that we think will complement future changes we want to do in the language).
In this case, we found that we could provide high bang, for low buck, for a small audience. In balance, we felt that this was worth it. If you feel like that’s “selling out”, then i’m not sure what to tell you. Except that we will continue to do this sort of thing as we’re constantly seeking out ways to make our language better (sometimes for the majority, and sometimes for a minority).
The language team will do what we always do. Assess the needs of customers and determine if and what we feel will be best for the language. If you want to change that, you’re welcome to come join us and bring your own judgement to bear in that process.
Until then, i imagine you’ll have to continually be disappointed that we we don’t have a special process for ensuring that the changes we make will meet with your approval before we do them 😕
I think everybody basically agrees and is just having different reactions to some apparent trolling on @controlflow’s part 😆
@CyrusNajmabadi
Right. My post was in the context of this proposal, where @controlflow made it sound as if
ref
-returning properties are the way of the future and should have every convenience possible (specifically, auto-properties).7% can indeed be a lot. But considering all the disadvantages of
ref
-retuning properties, and how limited my benchmark is, I don’t think it’s enough to start switching to them on a large scale. And so, in my opinion, the convenience of havingref
-returning auto-properties is not that important. (Havingref
-returning properties in the first place is another matter entirely, because it’s not just about convenience.)You’re taking things out of context. The number of scenarios where
ref
returns are truly useful is pretty small but has high impact. For exampleref
returns allows theSpan<T>
indexer to behave like the array indexer. Simply having the ability to create array like types is alone enough to justify the presence ofref
returns in the language.Considering your replies in that Twitter conversation I take it that this is indeed a joke.
There’s nothing modern about exposing a private field via a
ref
. It’s like making the field public. You get the same features - lack of validation, lack of property change notification, lack of control over the property backing store etc. There are very few reasons to create such properties.I don’t think there are enough useful scenarios for that. This “modern property” thing is certainly not an useful scenario.
@CyrusNajmabadi,
And that right there gets to the heart of the issue. Fix this and C# will be the most most perfect language, ever (until I change my mind next week on what ought to be disallowed of course) 😆
@DavidArno I can no longer make sense of your argument. I originally responded because you stated:
I disagree with this. And it was a core design goal of the feature that this be possible. Many (most?) of the customers that wanted this feature, also very much wanted mutation as well.
Now you’re saying that we should be messaging:
On one hand, you say that mutation of refs is a ‘misuse’. Now you say that it can be appropriate to “write faster - and cleaner - code.” and that for people with mutation needs “ref returns/locals are for you”.
How can it be a misuse when you yourself have identified precisely an audience for which this is an extremely desirable property of the system? To me, it sounds like you’re saying exactly what was being said back to you. Namely that there are good reasons for this, even though you emphatically claimed “They aren’t intended to allow you to start modifying such data.”
I literally just said: “This feature is primarily for that edge case. ‘ref’ is extremely advanced, and is meant for those who need to squeeze out this sort of performance.”
I can’t help you with that. When we’re presenting this feature to new audiences we spend time discussing when and where this feature is appropriate and when and where it isn’t. People presenting the information have routinely mentioned that it’s for extremely niche scenarios, and unless you’re someone working with arrays of large structs (like a game developer), and you need to operate on that data with as little overhead as possible, then this feature likely isn’t relevant to you. I’m virtually certain i’ve seen presentations that said something akin to “in fact, this is likely so not-relevant to most people here, that we’re not even going to spend further time on this, as there are vastly more relevant features that we should cover instead. If you’re in a domain where you need to squeeze this sort of perf out of the system, you’ve probably already been wanting this, and can learn more. Otherwise, it’s almost certainly not for you.”
But, given that, that doesn’t mean that every sentence out of my mouth is going to repeat that information. At some point repeating the same information gets old, and it interferes with having an actual discussion about the topic.
I get that you want the language team to design the language in a certain way. And message the language in a certain way. And do all sorts of things a certain way. But you have to accept that you’re dealing with humans, operating with a whole host of challenges, and communicating with many different mediums. It’s virtually guaranteed that across all that, it will be common that what we do and say doesn’t align with what you want or what you want to hear. This is especially true in Github. I’m going to talk and discuss and weigh out issues. And trying to craft the message such that you find it acceptable, is simply not something that i’m going to spend brain cycles on.
Agreed.
Then don’t mutate variables.
This feature is primarily for that edge case. ‘ref’ is extremely advanced, and is meant for those who need to squeeze out this sort of performance. We’re not going to create the feature to address the problem for this set of users, and then go and hobble it so they can’t use it.
7% sounds absolutely massive to me. We’re happy when we introduce a compiler change that gets a <1% improvement on code.
For domains where people are trying to squeeze out all the perf they can (i.e. the same people asking for ref-returns in the first place), such improves are huge.
Remember that ref-returns are for people who still want to code in a safe language like c#, but want to be ‘as close to the metal’ as possible. i.e. things like game-engines and whatnot. In these domains these percentages matter. You may get a few more fps from this. Or this may open headroom for more features. We did ref-return in the first place precisely for perf. So getting more perf seems like a super great thing to be able to provide 😃
@eyalsk I just don’t know what’s the rush to ship ref locals before readonly locals. i.e. as soon as possible! Before doing something about immutability, I think that’s just wrong. Perhaps if you ask, “it’s highly requested feature” is the best answer that you might get. And we all know how customers request features.
@alrz and @orthoxerox,
You are both, of course, correct. I still get frustrated though that their “neutral position on the question of mutability” position inevitably leads to things like this request.
At least @dsaf knows how to write good code, and hopefully soon:
really will become the modern way of writing code 🎉
@DavidArno As long as we don’t have
readonly ref
there is nothing you can do to prevent it.There is nothing “modern” about micro-optimisation. Did you mean “expression-bodied”? Modern is going to be like this #5172 :
PS: I personally hate exposed default mutability - not setters.
First:
This type of language is just not necessary. These are tools for solving problems.
Second, I believe ‘ref’ to be a fairly advanced feature intended explicitly for driving performance in constrained scenarios. The customers requesting this feature do not want ‘ref’ purely to get readonly data back. They want ‘ref’ returns so they can easily get access to and mutate values in their system.
One of the common cases for this is a game engine that has large numbers of value types representing pieces of their system. Then, may times a second, they go an update/mutate these values. These individual values may be so large that passing them around is actually a perf issue. So passing them by-ref is a valuable savings. They get the benefits of:
–
Mutation of ref-values is not viewed as a negative. It’s viewed as a core reason why we did the feature in the first place.
I was curious about this. Turns out there is a difference in performance between the two, but it’s fairly small (depending on your point of view).
First, decompiled assembly:
“OldSchool”:
“Modern”:
Benchmark results (using x64 .Net Framework):
So for my benchmark, the difference is about 7 %, which doesn’t sound like that much to me, considering what you lose by switching to such “modern” style.
Not really, arrays aren’t resizable.
Sort of but not really. My example with list resizing would have resulted in memory corruption/crashes in C++. In C# nothing truly wrong happens, the element list won’t be updated but there are plenty of ways to screw up in any language.
ref
starts to feel like C pointers, viral and unsafe only in C it’s more succinct, isn’t the whole point of ref is to be an abstraction over pointers and safe to use everywhere, like without gotchas?In C# 10 we might need unique_ref, shared_ref abstractions similar to C++, go figure. 😆
@DavidArno you should’ve guessed by now that the design team holds a neutral position on the question of mutability and will not make things immutable when they can be left mutable. Properties can already be made ref-gettable (I actually wonder how much faster
List<T>
will be with a ref indexer), so the only reason for not having ref-gettable autoproperties is nudging the programmers into the pit of success, since ref-gettable autoproperties are basically fields.@controlflow,
There is nothing “modern” about misusing
ref
returns to make your fully publicly mutable fields look like they are immutable.This misuse of
ref
returns is a joke, whether you intended it to be or not.@controlflow I’d agree with @dsaf. If you are going to expose it like that, there is no reason to use an auto-property (at least not something that I can think of).
@mikedn how is this a joke???!? Jared Parsons claims passing properties by ref is a highly requested user scenario: https://twitter.com/jaredpar/status/804070933954633728
I believe him! And it is now possible to express such modern properties in C# 7.0! The only thing I want is to reduce boilerplate and extend auto-property syntax just a tiny bit.