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)

Most upvoted comments

modern property pattern

With all that benefits in mind, I don’t see a single reason to use old-school properties again!

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.

How would one lose index validation?

For example:

l.Add(42);
ref int x = ref l[0];
l.RemoveAt(0);
x++;

It’s actually worse:

l.Add(42);
ref int x = ref l[0];
l.AddRange(Enumerable.Range(1, 10000));
x++;

AddRange will likely result in array resizing and now x points to element 0 of the old array. Good luck modifying the element 0 of the list via x.

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,

Mutation of ref-values is not viewed as a negative

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

… mutation of values is always seen as a negative,

Then don’t mutate variables.

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.

In the current “let’s sell out the language to the highest bigger” world of the C# language team,

@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 no doubt help to lower the quality of the language further by encouraging premature/micro optimisation behaviour, such as the inappropriate use of ref.

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 having ref-returning auto-properties is not that important. (Having ref-returning properties in the first place is another matter entirely, because it’s not just about convenience.)

Jared Parsons claims passing properties by ref is a highly requested user scenario: https://twitter.com/jaredpar/status/804070933954633728

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 example ref returns allows the Span<T> indexer to behave like the array indexer. Simply having the ability to create array like types is alone enough to justify the presence of ref returns in the language.

how is this a joke???!?

Considering your replies in that Twitter conversation I take it that this is indeed a joke.

And it is now possible to express such modern properties in C# 7.0!

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.

The only thing I want is to reduce boilerplate and extend auto-property syntax just a tiny bit.

I don’t think there are enough useful scenarios for that. This “modern property” thing is certainly not an useful scenario.

@CyrusNajmabadi,

C# isn’t going to add features to make every single thing that David doesn’t like disallowed

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:

Using ref returns to modify that data is a misuse of this feature.

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:

If you really need those values to be passed by reference and to mutate, and understand the implications, then ref returns/locals are for you. They’ll help you write faster - and cleaner - code.

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.”

The sort of advice I’d hope to see coming out of the language team would be: Ref returns are a niche feature.

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.”

What I’m hearing instead

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.

I didn’t explain that properly

Agreed.

In my world though … mutation of values is always seen as a negative,

Then don’t mutate variables.

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.

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.

So for my benchmark, the difference is about 7 %, which doesn’t sound like that much to me,

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.

@DavidArno As long as we don’t have readonly ref there is nothing you can do to prevent it.

@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:

var p = new ModernPerson(...);
var q = p with { Age = p.Age + 1 };

really will become the modern way of writing code 🎉

Using ref returns to modify that data is a misuse of this feature.

@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 :

var p = new ModernPerson(...);
var q = p with { Age = p.Age + 1 };

PS: I personally hate exposed default mutability - not setters.

First:

This is a downright offensive misuse of ref returns.

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:

  1. Reduced GC (or maybe no GC).
  2. Inexpensive passing of values.
  3. Easy mutation of those values in parts of their program that make sense (instead of all mutations having to be forwarded to the place where the value types are stored).

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.

New modern property pattern has improved performance of compound operators:

var person = new ModernPerson { ... };
person.Age ++; // no setter call!!

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”:

mov      edx, dword ptr [rcx+16]
inc      edx
mov      dword ptr [rcx+16], edx

“Modern”:

mov      r8, rdx
inc      dword ptr [r8]

Benchmark results (using x64 .Net Framework):

Method Mean StdDev
Modern 2.3388 ns 0.0585 ns
OldSchool 2.5128 ns 0.0303 ns

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.

Yes, and that’s what will happen when you replace the list with an array as well. refs are dangerous.

Not really, arrays aren’t resizable.

ref starts to feel like C pointers, virality 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?

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.