C# 11 is nearing completion. This post covers features that are new in 17.3 or were not covered in our April update on Visual Studio 17.2 and our February update on Visual Studio 17.1.
The new features in this preview follow on three themes of investment for C# 11:
- Object initialization improvements: You can support constructors and object initializers in your type easier, independent of the rules you want to enforce for mutable and immutable members. Features include:
- Required members
ref
fields
- Generic math support: You can write algorithms once for multiple numeric types. These features make it easier to use C# and .NET for statistics, machine learning, and other math-intensive applications. Features include:
- Static abstract and static virtual members in interfaces
- Relaxed right-shift requirements
- Unsigned right shift operator
- Numeric
IntPtr
]
- Developer productivity: We’ve added more language features to make you more productive. The extended
nameof
scope feature is new.
The sections below provide an overview of each feature and links in Microsoft Docs where you can read more. To try these features, you’ll need to enable preview features in your project. That’s explained in the What’s new in C# 11 article in docs.
Improved object initialization
Required members let you write class and struct types that require callers to set certain properties. Consider this Person
type:
public class Person
{
public string FirstName { get; init; }
public string LastName {get; init; }
}
Callers should use object initializers to set the values of the FirstName
and LastName
property. But prior to 17.3, the compiler can’t enforce that callers must set those properties. A constructor that requires parameters is the only way to ensure the user sets the FirstName
and LastName
properties. Required members communicates to the compiler and callers that they must set those properties. Add the required
modifier to the member declarations:
public class Person
{
public required string FirstName { get; init; }
public required string LastName {get; init; }
}
All callers must include object initializers for the FirstName
and LastName
properties or the compiler emits an error. The compiler informs callers that required members weren’t initialized. The developer must fix the problem immediately.
If the Person
type was written for an earlier release and includes a constructor that sets properties, you can still use required members. You should annotate any existing constructors with the SetsRequiredMembers
attribute:
public class Person
{
public required string FirstName { get; init; }
public required string LastName {get; init; }
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public Person() {}
}
The SetsRequiredMembers
attribute indicates that a constructor sets all required members. The compiler knows that callers using the Person(string firstName, string lastName)
constructor have set the required members. The parameterless constructor doesn’t include that attribute, so callers using that constructor must initialize all required members using object initializers.
The examples above used properties, but you can apply required members to field declarations as well.
This preview also contains an initial implementation of ref
fields and scoped
values. These changes provide the capability for ref
fields in ref struct
types. You can also use the scoped
keyword to limit the lifetime of ref
parameters. The feature proposal and updated changes provide the best documentation on this feature right now. We discovered some scenarios that required language changes to be used safely. The updated changes will be available in a later preview, and the documentation will reflect the final design.
Generic math support
We’ve added features where the motivating scenario was generic math. You’ll only use these features directly in advanced scenarios, such as writing mathematics algorithms that work on multiple number types. Otherwise, you’ll benefit indirectly because the runtime uses these features:
- Static abstract and static virtual members in interfaces
- Relaxed right-shift requirements
- Unsigned right shift operator
- Numeric
IntPtr
The addition of static abstract and virtual members in interfaces provides much of the important infrastructure for generic math. This feature allows interfaces to declare operators, or other static methods. Classes that Implement an interface must provide the implementation of static abstract
methods, just like other methods declared in interfaces. The compiler resolves calls to static
methods, including operators, at compile time. There’s no runtime dispatch mechanism as there is with instance methods. The docs provide more details on the specific language rules required to make this feature work.
Other language features smooth out some differences in numeric types to make it easier to write generic mathematics algorithms. The right-shift operator no longer requires the second operand to be an int
. Any integral type will do! The nint
and nuint
types are synonyms for System.IntPtr
and System.UIntPtr
, respectively. These keywords can be used in place of those types. In fact, new analyzers will gently nudge you to prefer the keywords to the type names. Finally, the unsigned right-shift operator (>>>
) avoids casts when you perform an unsigned shift.
Combined, these changes and other changes like checked operators support the generic math runtime changes. The language improvements mean the runtime team can provide improvements across all numeric types in .NET. You can also leverage the features when your types implement contracts using operators, or other static methods.
Developer productivity
The nameof
operator now can be used with method parameters. This feature enables you to use the nameof
operator in attribute declarations on methods, like the following example shows:
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
Give it a try
Please download the latest Visual Studio 2022 Preview and install the .NET 7 preview or you can separately install latest preview of .NET 7. Once you have it installed, you can try out the new features by creating or opening a C# project and setting the LangVersion
to Preview
.
This Visual Studio preview gets us closer to the complete feature set for C# 11. We’ve continued to invest across multiple themes in this release. We’ve made corrections along the way based on the feedback you’ve already given us. Now is a great time to download the preview, try all the new features, and give us feedback. We’re listening and making final updates for C# 11 and .NET 7.
Shouldnt it be the other way around? [DoesNotSetRequiredMembers]. I assume a constructor always sets required members and this attribute could be inferred on empty constructors.
Hi,Is there still hope for “field”
The required members thing does not sound good. More useless bloat. No value.
Feature set on c# should have been closed a long time ago.
“Improved object initialization”
You’re just creating non-sense really.
Could we just replace init with required? To me, required implies init…
Second that. There are already too many context aware keywords with very questionable value.
For example, "required" is only once concern, but next MSFT may vote to add "good" specifier to designate
assignment that only take "good" data. And yes, one can amass 1000pages proving that there is a way and some value
in defining "good" vs "bad" data. Purism at its best.
The problem starts when "buzzword marketing" start making language design decisions.
As stated...
The feature I was looking forward to the most (semi-auto properties) seems to have got cut and moved to the next version, without much notice; other than following the issue on github and someone having actually asked, the only indication has been that posts like these stop mentioning it.
I know that there are always disclaimers that anything in early development or preview status might not end up in the final release, but I still feel...
Hi Stuart,
I understand your frustration. However, I think that much additional communication will be more noise than signal. I watch https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md to track when features are likely to make the next release Once the State column says "Merged in ???*, that feature will be available in a public preview. My aspirational goal is to have the docs ready by the public preview. I don't always meet that one, but I get close.
That's still not a...
That's vary fair. I've generally been watching the individual github issues because that way I get actively notified of changes rather than having to periodically check for them, so I was surprised when the target release had changed without even being mentioned in https://github.com/dotnet/csharplang/issues/140 until someone happened to ask. But as far as public discussion I can see the potential for the signal-to-noise ratio not being worth it. Maybe a footnote on posts like this...
All in all, very interesting set of features, just SetsRequiredMembers doesn’t feel like C# rather one of those kludges that C++ is known for.
Exactly.
I seem to fail to understand the difference:
<code>
and
<code>
Does the second example mean that if we never e.g. assign in the object initializer (as it's only optional) then we're not going to be able to assign it later?
The second example has been legal for two releases now, and means something. With very rare exception, we do not make breaking changes to code. Further, there is no way to determine whether or have been given reasonable defaults by the original type author, as the user could have written this and it would look exactly the same:
<code>
Hi @Fred Silberberg, regarding “we do not make breaking changes to code” I rather prefer breaking changes to unreadable and non-intuitive syntaxis.
If one wants to move from version X to X++, one has to accept not just new features but corrections as well.
Just letting you as seem you are an active part of the development of this awesome language.
But the compiler checks in a way that defaults are set. Because if you don't, you get the warning "Non-nullable property 'FirstName' is uninitialized. Consider declaring the property as nullable."
The fix, as we know is to initialize with 'null!'. Which goes against declaring it as a non-nullable in the first place.
What I fail to understand: When nullable is enabled, which is optional, why can't you postpone that check until first usage or when it goes...
+1
I think we don’t need
required
keyword theinit
+reference nullable
should means “required” , what we need is actullyWhat it means is that it is time to start thinking about getting away from C#.
The insatiable desire to "add value" by adding features turns languages into a very complex set of "gotchas" that are really not needed.
The very fact of questions like this arising is a testimony to that.
As stated before, the "required" means something to "compiler" , but what des it mean for a developer? Absolutely nothing.
Suppose I set a...
Sad to read criticism but not proposals.
The “insatiable desire to add value” makes bigger and better things. We have the opportunity to make it better or run away.
Very well said. Thank you.
+1
Mark Seemann has an interesting blog post about just that: https://blog.ploeh.dk/2022/08/22/can-types-replace-validation/
The short answer is: No you can’t.
+100 to this
During the design of the feature, we commonly saw people conflate nullability and requiredness. Unfortunately, this is a mistake: properties can be
struct
s , for example, or you might want to require a nullable property (ie, thatnull
is a valid value to set, but you want your consumer to make that decision intentionally).Maybe you should have made a poll on how many of us thinks ‘null == optional’ and ‘not-null == required’? If the majority thinks that, then that is what it means.
I, for one, think logic is off when a required property can be null.
Load the ointernet favucon