Dealing with limited breaking changes in C# #7033
Replies: 42 comments 193 replies
-
What about something like Haskell's LANGUAGE pragma - if you want to stick with C#11 but pick up the new meaning of <PropertyGroup>
<LanguageVersion>11</LanguageVersion>
</PropertyGroup>
<ItemGroup>
<LanguageExtension Include="FieldKeyword" />
</ItemGroup> Or alternately, if the whole project is C# 12, an individual .cs file could opt out by doing <PropertyGroup>
<LanguageVersion>12</LanguageVersion>
</PropertyGroup> #language NoFieldKeyword
class C {
private string field;
public string Property { get => field; set => field = value; } // Property is backed by this.field
} |
Beta Was this translation helpful? Give feedback.
-
If you ask me, I would accept breaking changes for the sake of going forward and allow improved designs. We carefully watch what the language and runtime team do and adjust our coding accordingly so that we are prepared for new things. That was the reason why the transition to .NET (Core) from the .NET framework was a matter of weeks. I wouldn't prefer something silent, because I have learned from the past that simply making things not showing a problem, will be a problem later. So I would accept an error with a fix for the edge cases you mentioned. Meanwhile (while I was typing) @lambdageek already posted something I also would be fine with, namely opting in or out. I don't know about the efforts and how easy it is to add features as options, but this would be something that could be used in a wider scope. Instead of just switching the From our product code, breaking changes is always a problem since we have strong approval processes in medical imaging. So I would think my other colleagues would disagree here, but they are also still on .NET Framework 4.8.... As said above, this is my opinion, there might be other perceptions. |
Beta Was this translation helpful? Give feedback.
-
I have concerns about anything that has the potential to create schisms in the ecosystem. The switch to .NET Core is still painful and insurmountable for a huge number of projects. Having the language add more paper cuts for each revision of the language only makes that more difficult. That's not to say that I'm completely against deprecating and breaking changes to the language. I've argued that this should have been the tactic adopted for discards, for instance. Having the ability to break glass in the back pocket for exceptional cases I think is a good thing in general. But I think we need to be very cognizant that every little break like this makes it harder for projects maintained by less passionate and devoted developers to keep up with new versions. |
Beta Was this translation helpful? Give feedback.
-
I don't think preventing hypothetical confusion of developers outweighs confusion and frustration of those who suddenly find their code (or code they had from elsewhere) not working. If you instruct a developer about contextual keywords, they will know about this for the rest of their lives, but I guarantee you will always be able to discover code that broke as a result. We can all move forward here, but all existing code won't. |
Beta Was this translation helpful? Give feedback.
-
Generally I think that this is a good thing and a step in the right direction for the language, I know the LDT is pretty mindful and careful about the changes so personally I'd welcome it. It would be nice to have a document where all these breaking changes are documented per version of the language as well as treating these changes similar to features where they are discussed with the community, I know many of the LDT members are doing it actively just thought to bring this up because I haven't seen it in the OP. :) |
Beta Was this translation helpful? Give feedback.
-
This proposal is spot on. As a library maintainer, I've also long wished that tooling could present warnings and automatically fix up code during library package version upgrades, for example to move away from removed deprecated library APIs. Both of these aspects would be important:
|
Beta Was this translation helpful? Give feedback.
-
Would there be a way to possibly have these new warnings be auto-excluded from warnings-as-errors? Sometimes we aren't in control of the exact installed SDK version, say on a shared build server where we want the updates without resorting to a global.json. I wouldn't want the warnings now appearing from a minor SDK bump to suddenly stop code from compiling at all |
Beta Was this translation helpful? Give feedback.
-
I think this makes perfect sense. Especially with the careful approach as outlined in the OP, I don't expect it to be a problem. One thing I would add, is there a plan to try measuring the impact of the breaking change in real life? Like some sort of big data analysis of all public and maybe even private (per agreement with customers) code, to determine if it's a concern at all? For example, with field keyword, do people really have many variables with that name? I'm not saying they don't, just asking if it's feasible to measure |
Beta Was this translation helpful? Give feedback.
-
How about introducing something like the concept of edition in Rust? When you want to make some major breaking changes to the language, you can bump the edition, such as 2022 (assuming C# release a new edition every 5 years), so users can choose which edition of the language at their own risk. An update assistant can also be developed to help users fix source compatibility issues during edition upgrade automatically. As for the language team, maintaining only three editions can you already support users from a range up to 15 years. This also enables the ability to withdraw some bad designs that already existing. Typically, we want C# to clean up some old features that being superseded by new features. For example, when we have roles and extensions, we don't need extension methods anymore, so we would like C# to remove the old extension methods so that it can prevent the language from bloating. We don't need plenty of ways to express a same thing. But as the language evolves it is not avoidable, so we need a mechanism that allows us to remove outdated things from a language. With editions, the language can always revise the existing features again, fixing or even removing outdated/misdesigned features recursively, in a period of several years. This is essential and can keep a language clean and efficient. |
Beta Was this translation helpful? Give feedback.
-
I really like the way Rust handles breaking changes with editions. Instead of spreading small breaking changes across the releases they bundle them into editions that are currently spaced three years apart (in C# years that's like 10 years apart) and you can automatically upgrade to the next edition with a single |
Beta Was this translation helpful? Give feedback.
-
How would this work with the VS IDE experience? Whilst I'm not familiar with the exact intricacies, it is quite clear that the in-IDE analysis seems to use whatever latest version of MSBuild and Roslyn that it shipped with, even when the actual project compilation uses a different version thanks to I wouldn't want to be in a position where I have a project on SDK 7 that uses 7 defaults (no explicit To take that one step further, let's say I have a .NET 7 / SDK 7 / C# 11 project, and I set it to |
Beta Was this translation helpful? Give feedback.
-
You seem to forget there are (well... allegedly) some code bases out there that on purpose define real types named |
Beta Was this translation helpful? Give feedback.
-
A possible fix for existing |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
The supported language version needs to be specified in the single file. |
Beta Was this translation helpful? Give feedback.
-
Would it make sense to add breaking change warnings at some (not necessary the latest) level of warning waves? It's already possible to get those on a specific version since there's no relationship between <!-- we're at 7.0 for some reason but give me all the warnings -->
<LangVersion>7.0</LangVersion>
<AnalysisLevel>latest</AnalysisLevel> For a more explicit option, the SDK already provide |
Beta Was this translation helpful? Give feedback.
-
New keywords or standard identifiers could be introduced as invalid C# identifiers, like |
Beta Was this translation helpful? Give feedback.
-
This might already be a thing, but having some sort of source-control level analyzer notifier of these sorts of breaking changes, where the owners of the embeded repos are emailed when their repository is detected to have old lang ver projects that will will break in future versions. It could theoretically inspect all the same settings as mentioned in the OP and maybe even auto-submit PR's/issues for fixes, but the important bit is just getting the email warnings. In my mind, this would work in a similar way as the github dependabot, just customized and tailored to the C# breaking changes experience. Not sure if this possible/practical, but something that would be needed for main stream source control providers (i.e. Github and Azure DevOps). Might require a one-time setup per org, not sure. The end goal being to ease an organizations upgrade process to new lang vers with breaking changes, since at an org/team level, we will have, potentially, multiple dozens of projects under our scope. |
Beta Was this translation helpful? Give feedback.
-
This proposal sounds acceptable to me. I started out not liking it, but the 4 criteria established above under Language Design Considerations are enough for me to see the light. The first thing that pops into my mind when we see a breaking change - "who asked for this and why is their use case more important than mine?" The other edge of the sword is that the ever-forward progress of the C# ecosystem has gently nudged us towards higher-quality code. Biggest among these progressions being null warnings-as-errors and related corrective measures. Backwards compatibility is a really important topic for me, but I recognize trying to achieve 100% coverage would become extremely prohibitive to the roadmap (i.e. stuck in a local minimum). |
Beta Was this translation helpful? Give feedback.
-
I definitely don't want to derail this by going too deep into a specific example, but I figured I'd at least mention the inconsistency between See the following for more details:
Again, I'm not requesting any discussion on this specific example here (that can be done in either of the two discussions linked to above), but I think having more examples is helpful since there's probably a lot of nuance around what kind of breaking change is worth including in this proposal and what isn't. |
Beta Was this translation helpful? Give feedback.
-
Just break things. I know that's not going to happen, but I'll ask for it anyway. The cost of debt is infinite over time. C#/.NET are 23 years old and long overdue for breaking changes to right the past... I hope in 2050 we aren't still dealing with today's legacy brokenness from 2003, but as of today we are 100% on that path. Minor breaks are a good start. Would collections or reflection look the same if redesigned today? Probably not - they'd probably at minimum have better interfaces... How about IL, the build process, or native interop? They'd probably be quite different too; today we can't declare nullable as a constraint or use attributes on loops/variables/statements, which would be nice for GPU / high-performance computing, and presumably codegen would be quite different & projects would be structured differently with multiplat AOT in mind. The native interop story is still pretty painful; the VS tooling is great, but the language support is still barebones. Yes, these are all seemingly minor, but they add up into pretty giant cracks. I frequently wish what Kotlin did to Java's stdlib would happen to C#, or that .NET were split into .NET Enterprise Version stuck in 2001 alongside a .NET Modern that tries to do things right. |
Beta Was this translation helpful? Give feedback.
-
Generally in favor...and thats mostly because it wouldn't make a difference for most of my code bases (which use underscore-camelcase for backing fields whenever it isn't an automatic or read-only property). However: Code style (and its enforcement through |
Beta Was this translation helpful? Give feedback.
-
The problem with this proposal is that it treats silent breaking changes and previously-legal-but-now-an-error as nearly equivalent. They are not even remotely equivalent. Errors are loud, easy to find and easy to fix. Silent breaking changes are a nightmare- warnings will not work as most devs have learned to ignore them (they are so often spurious). User's dealing with breaking code will always be orders of magnitude more expensive than those costs born by the compiler team. The That said, more interesting to me than anything in this breaking change proposal is going the opposite direction- turning some of C#'s keywords into context-sensitive keywords. It is silly that |
Beta Was this translation helpful? Give feedback.
-
Im probably at the extreme and fringe end of the scale with my viewpoint but, i'm all for breaking changes with very little regards to user "happiness". In my opinion changing major versions in software, you should expect breaking changes. I followed .NET framework for a long time and the mantra to "avoid breaking changes at all costs" really hindered the language. .NET Core was a breath of fresh air as it wasnt constrained by the same principles and a lot of the features we're drastically changed from its Framework co-parts (and for the better!). I can't tell you how much I hate having to maintain older .NET framework projects, and going back and doing so, really makes you appreciate how much better .NET Core / 5+ is. I've seen a few features in .NET core / 5+ that have been introduced, where the implementation team weren't too confident on how well the design of said feature would be received, only to find that once it had been released, it was a mistake or retrospective information allowed people to see the pitfalls of said design. These features then have solidified because they're "shipped" and the API is public so any modifications would be really hard without breaking changes. Being more liberal with breaking changes would really help in those situations where the API isn't the best and improvements after the fact, can be made. The only thing that I would require is that breaking changes are not only documented in release notes, but expectations of what to do with the breaking changes are also provided. In the past when niche API's have been obsoleted in favour of new mechanisms or API's, the end user is given no help. In these cases, I dont think a "fixer" is enough as the fix proposed might not be what the end user is wanting to achieve. If the relative teams have time to produce a "fixer" / "analyser" for the problem, then they should have the time to write an in-depth section about why the breaking change has been made, what the expectations / recommendations are to migrate (with examples), and links to relevant resources. |
Beta Was this translation helpful? Give feedback.
-
I support this proposal, in order to move forward, it is necessary to accept the removal of things that are no longer relevant and hinder development, because over time complexity and obsolescence will be a burden. and gradually take up all the space for development. The language will die over time and when it is realized, there is no time to fix it due to too many outstanding problems. But make it clear, friendly, easy to switch between versions, and give error messages and documentation. Maybe like kotlin, rust... do. |
Beta Was this translation helpful? Give feedback.
-
Sorry if I missed it but is there a proposal for how code generators would work in this world? With source generators becoming more common I can see this becoming a new point of friction with upgrades because now you have to get the generator to upgrade in order to upgrade the project. We have I think the ideal answer would be to allow for a Something else to consider would be any place you can write C# outside of a project, e.g. tools like LINQPad or inline snippets passed to things like |
Beta Was this translation helpful? Give feedback.
-
Overall I support introducing sensible breaking changes (especially if it helps clean up the code an allow it to evolve). I also have a slight preference to make the code uncompilable rather than issuing a warning that could be ignored. Unfortunately, enabling nullability on a non trivial old code base increases the likelihood that a warning is missed. Hopefully, all these complications will not delay the |
Beta Was this translation helpful? Give feedback.
-
I think there should be breaking change warnings in the new language version as well, in case the developer upgraded the language version without encountering the breaking change warnings first, e.g. by upgrading the compiler and the language version at the same time (so there's no window of time with the new compiler and the old language version to be able to present the warnings). In the case of This would be really useful in cases where the code is still valid but silently changes meaning from one version to another. I think it's really important that nobody could ever miss such a change and have a silent bug introduced by an upgrade. In cases where the code becomes an error, a warning in the new language version wouldn't be necessary as the user would already know that something is wrong and would not get a silent bug. |
Beta Was this translation helpful? Give feedback.
-
I totally support this feature and I think it is an intelligent design to face "historical" issues in the language. as for the warnings, I would love to see a new category of warnings that are outlined specifically, because I have seen way too many projects out there, where devs either didn't care or couldn't make the time (because they inherited older legacy project) to fix warnings and therefor have projects/solutions that have already multiple hundreds of warnings. in those cases they might overlook these breaking warnings and I think k it might make sense to introduce a new category maybe called "breaking warnings" or so, that the ide could highlight better. as for the opt in why couldn't we just make |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Dealing with limited breaking changes in C#
Oftentimes new C# language features have edge cases that could conflict with existing features. In the past we’ve avoided breaking existing code even at the expense of making the feature design more complex than otherwise needed. Sometimes the burden is mostly one of implementation complexity borne by the team, but other times aspects of the feature end up being genuinely confusing to developers as a result.
One example is discards. Because
_
used to be just an ordinary identifier, the language tries to protect existing usages as such. As a result,_
sometimes represents a discard and sometimes an ordinary identifier, depending on whether or where it is declared. The rules for this have little inherent logic, as they are based purely on "what was there first". This is needlessly confusing to anyone just trying to learn and use discards, and limits the places where they can be usefully employed in code.What if we changed our stance a little here? C#'s commitment to backwards compatibility is very valuable to its users, but so is simplicity and clarity. Could we adjust our approach so that the existing language can "move over a bit" and give new features room for more consistent and elegant designs?
For this to be a good trade-off, it would require a high-confidence, low-friction mechanism for making existing code resilient to such new features. And it would require a lot of thoughtfulness and restraint on the language design team to only introduce breaking changes with limited scope, strong justification and excellent fixes.
In the following we'll try to outline such a mechanism. We're curious about everyone’s thoughts, both on the specifics of the mechanism, and on whether it represents a balanced approach to considering limited, thoughtful breaking changes in C#.
Example: field access
Field access in auto-properties is a feature we would very much like to add to C#. The feature allows a variable name
field
to be used in property accessors to refer to the generated backing field:However, the above code could already exist, and could for instance be referring to an existing variable called
field
in an enclosing scope. If that code now changes meaning in e.g. C# 12, this is a breaking change - either it leads to an error or to different behavior of the existing code.In order to avoid such a break, the current proposal for
field
introduces the newfield
keyword conditionally - only if there isn't already anotherfield
in scope at the point of the accessor. This "works" in terms of not breaking existing code, but leads to more confusing behavior for the new feature itself. Introducing afield
field somewhere outside the property can nullify the language feature and inadvertently change the meaning of occurrences of thefield
identifier that were intended to refer to the backing field. This behavior is arbitrarily inconsistent with howvalue
works today in property accessors, simply becausevalue
was in the language from the start. Finally, the implementation and maintenance costs in the compiler and tooling are likely to be higher because the feature is more complex and the binding behavior less localized.It would be better if we could just make the lookup of the new
field
identifier "normal", and consistent with every other identifier in every other place in the language. This is the kind of breaking change that would make the new feature design meaningfully simpler, and leave the language with fewer “gotchas” in the future. Instead of shying away from these kinds of breaking changes entirely, could we give you a mechanism that reliably protects your existing code in the relatively rare cases where the new feature would break it? Could we help you easily find and fix any lines of code that would be vulnerable to the break, before you even adopt the new language version?Outline of a solution
As a matter of terminology, a breaking change is a change to a newer version of the language that will cause some previously working user code to break. There are two kinds of breaks that can occur that the language concerns itself with:
The first is actually the most benign. At least you'll know that something is up, giving you a starting point for chasing down the problem. Still, it is not ideal: The error may not easily indicate that the cause is a breaking change in the language, and it may not even occur at the relevant point in the code.
The second is more insidious. Because the behavior change may be subtle it not be caught in testing, and could lead to bugs down the line where they are more costly. At the same time, it is clear from the
field
example that we can't just limit breaks to new errors.A mechanism to fully mitigate these problems should do the following:
Language design considerations
No matter how automatic and reliable the mechanism to mitigate it, any breaking change is going to have non-zero impact on at least some developers. There should be a high bar for language design decisions that lead to breaking changes:
field
, would be reasonable, but it shouldn’t be much more open-ended than that.field
identifier will refer to a generated backing field for the property."field
identifier, the fix would replace them with member access expressions, as inthis.field
andMyType.field
.If we cannot satisfy these requirements, we shouldn't adopt a given breaking change. We should either settle on an alternative design for the new feature, or punt on the feature completely.
Example: Getting ready for field access
Let’s assume that your current source code is in C# 11 or earlier, and has some occurrences of
field
as an instance field, which are being referenced as simple names from inside property accessor bodies:Now let’s say that C# 12 is released, and offers the
field
access feature with the breaking design. Without mitigation, upgrading your project to C# 12 would break the code above.The default fix for this breaking change would be to turn
field
inside property accessors into a member access; i.e.this.field
for instance members andMyType.field
for static members. This fix would change the above property declaration to:Of course you might want to fix the break differently, e.g. by renaming the
field
variable.How could we help you find and fix these breaks before you upgrade to C# 12? In the following, let’s sketch a user workflow for that in the context of an IDE and a CLI, using the
field
feature as a running example. Please note that this is just in order to paint a vision; the actual tooling experience will be designed by the appropriate teams, with the close involvement of the language designers. Afterwards we’ll go into more detail about how we could enable those workflows.The IDE experience
In the example, remember that you’re starting out in C# 11, and with C# 11 being the latest version supported by the compiler your IDE is currently targeting.
Now a new version of the .NET SDK gets released that includes the C# 12 compiler, and you choose to upgrade Visual Studio or the SDK directly. Based on your project settings (in ways we’ll come back to later), the following now happens:
field
is used in a property accessor, you get a warning along the lines of "In C# 12,field
in an accessor will refer to a generated backing field. Consider changing tothis.field
."field
tothis.field
: This is the default fix described above, and requires no further user input. You get the usual additional options to apply the code fix to all occurrences in the file or project, so if this works for you, you can be done very quickly!field
: If thefield
declaration can be edited in this project you might choose to address the issue by renaming it to something else.Separately from this, any IDE gesture that offers (directly or indirectly) to update to a new language version should be gated by the presence of breaking change warnings in your current code. It shouldn't be too easy to accidentally upgrade the language version without heeding the warnings.
CLI experience
Based on the same project settings as the IDE, a CLI build command would report the same warnings that are provided in the IDE. When you get these warnings, you can go and fix your code accordingly.
In addition, a CLI command (or .NET Tool delivered with the CLI that acts as though it is a CLI command) could be offered to apply the default fixes to remaining breaking code, whether across the whole project, or other granularities (directory?, solution?) that make sense.
You can also turn the warnings off if you have no interest in guarding against breaking changes right now.
Detecting breaking code
Detection of breaking code would be the responsibility of the compiler. It should be able to produce meaningful diagnostics on code that is compiled with a given language version "x", but will break in a higher language version "y" that the compiler supports.
It’s a long-standing practice that newer compilers continue to support older versions of the language in a way that is compatible with older compilers. Indeed this capability is used frequently, as most projects specify a fixed language version (often indirectly through the target framework) even while often being compiled by a newer compiler.
With this proposal, a newer compiler can add diagnostics to an existing language version. That’s not something we’ve done before! Needless to say, this behavior needs to be optional; something you can turn on or off. Depending on your viewpoint, such diagnostics are either a nuisance or exactly what you’re looking for. If you are not looking to upgrade your project to newer language versions, why should you care whether your code will work correctly in those versions? In such cases you would turn these diagnostics off. Conversely, if you do plan to upgrade the language version (or target framework) from time to time, then why wouldn’t you want newer compilers to tell you that your code is not forward compatible? In fact, since the compiler knows about the new language features (as it supports those newer language versions), it would seem distinctly unhelpful for it not to tell you what it knows about their impact on your code!
The upshot is that a given compiler version would use knowledge about newer language features to produce warnings in older versions of the language about code that would eventually be broken. If you heed those warnings, then your code will not break when you eventually upgrade to that language version.
Fixing breaking code
High quality code fixes representing the default fix of each breaking change will be implemented and offered as gestures in IDEs and commands in the CLI. While the fixes would not be part of the compiler itself, they should probably be implemented by the compiler team alongside the warnings. Their existence is an important part of the protective setup that would allow us to feel confident about new breaking changes, and building all the pieces at the same time by the same people is likely the most reliable approach.
Project settings
Perhaps the most hand-wavy phrase of the above example is “based on your project settings”. It would seem desirable that the breaking change handling adds or changes as little as possible about project settings, compared to what's there today.
The landscape is a bit complicated. For instance, in an SDK-style project, a project file without an explicit
<LangVersion>
will implicitly feed the compiler the language version associated with the specified<TargetFramework>
. That is indeed the most common setup. But elsewhere (e.g., when directly calling the compiler), leaving the language version unspecified will cause the compiler to use the latest language version that it supports, something that can also be explicitly specified with a language version oflatest
.The warning behavior proposed here relies crucially on the compiler being invoked with a lower language version than the latest one it supports. Only then do the breaking change warnings manifest! So for the large number of projects where a specific language version is provided to the compiler (even when it’s implicit in the project file) this will work quite well. When a new compiler comes out, it can start producing useful breaking change diagnostics relative to the latest version it now supports.
Opting in or out
Many projects will not want to ever upgrade, and should be able to opt out of warnings that are useless to them.
We suggest that we introduce a new compiler flag to guide version-related behavior, e.g.:
Opting out:
Opting in:
We need to decide which of the two is the default behavior if nothing is specified. This flag will "clutter" the project files of whomever does not want the default behavior. This speaks to having the default be "off", since that aligns with projects that already do not want to mess with their project files. Those projects could be left as they are today, and would never have to worry about this whole new breaking change thing that they won't care about.
On the other hand, if the default is "off", will too many existing projects that do upgrade from time to time neglect to turn it on, and miss out on the safety provided by the warnings? Is it really so bad for existing projects that don't want to ever upgrade to get a one-time warning with a fix to explicitly say no to upgrade-related warnings going forward? This way every existing project will be prompted to make a choice: Stay on the language version you're on, or fix code that would break in future language versions.
We'll need to make a choice here, and we're not sure which is the right way to go. One thing to remember is that, while the compiler has a default for what happens if nothing is specified, the SDK may have its own defaults for what is sent to the compiler if nothing is specified in the project files. The SDK also decides what is put in the project files when new projects are created in the first place. Finally, there can be a difference between what is the right default long-term vs what causes the least friction for the projects that exist today.
Latest version
For what's currently the language version of
latest
, whether specified implicitly or explicitly to the compiler, there’s a conundrum. With the current behavior,latest
causes the language version to automatically move up when the compiler is upgraded. But that behavior would side-step the very warnings that would make this upgrade safe!It seems that you can’t have your cake and eat it too. You can't have a new compiler both warn you about what would happen if you upgrade and immediately upgrade before you see the warnings! What to do?
We think
latest
no longer makes sense as a "language version" in this scheme. We should find a non-jarring way to retire it, and e.g. lock the default language version to C# 11 when you call the compiler without specifying a language version.Instead we can allow a stronger setting for the opt-in flag introduced above to express the intent to always be on the latest version:
The additional effect in the compiler would simply be that if the language version is not the latest and there are no breaking change warnings in the source code, then a warning is issued that you aren't on the latest supported version of the language. An IDE can then hang a fix off of that warning to actually upgrade your project's language version (as well as one to shut the warning up if you don't actually want to be on latest after all).
A variant of
latest
today ispreview
, which means latest plus any features currently in preview. We should move that over to the same flag, as in:That would enable preview features when the project is already on the latest language version, and have the same effect as
latest
when it's not.Accumulated breaking changes
If there is more than one version between the language version passed to the compiler and the latest one that it supports, some things could get gnarly. For instance, the same code could be subject to multiple subsequent breaking changes (hopefully very rarely!), and competing default fixes may apply.
Perhaps the fixes – whether in an IDE experience or a CLI batch fixer – should be sequenced according to the language version that introduced the break, so that a later fix is only shown – or applied – when the earlier breaking change has been dealt with.
Perhaps this is too much of a corner case to even worry about. We can learn along the way and adapt – it will not be an issue until the second time a language version introduces breaking changes!
Fixing past decisions
What about the concessions we've already made in the past in the name of back compat? What about discards,
var
,nameof
, etc. Could we fix those in future versions of C#?We don't see why not. Making those work more consistently and elegantly could be regarded as new "features" with breaking changes. But we'd have to be just as judicious as with "real" new features: Are the breaking changes worth the hassle to developers, and are we adding too many in any given release?
In some cases, "breaking" use of such identifiers is already discouraged, even today. For instance, using
var
as the name of a real type is a really bad idea anywhere, except in compiler tests! In such instances we might look more lightly at the cost of the break to users, because we'd assume most people are already not writing code that would be affected.Building the muscle
In summary, we propose to:
If we adopt a scheme like this we should start small - e.g. with one breaking feature (which should probably be
field
) - and learn from the impact. Over time we can tweak both the warning experience and the evaluation criteria for new breaking features based on what we learn from real world usage, until we've reached a steady state that represents a new and better balance between stability and evolution of C#.Any thoughts and reactions will be extremely helpful!
LDM Discussions
Beta Was this translation helpful? Give feedback.
All reactions