You can reduce the quality of photos to speed up the loading of your site
NET 5., the successor to .NET Core 3.1. And .NET Framework 4.8., Intends to bring a new cross-platform development experience to .NET developers. Offer. This brings new good features. Of course, in this article you will not learn everything about .NET 5., but you can focus on 5 to get a better understanding of what is happening.
If you own a website or work on a customer website, then you need to know the importance of SEO. Without proper SEO strategies, you can not get high rankings in Google.
Success in a media channel such as organic search depends on the content. In particular, the production of useful content has the ability to rank. In its recent algorithm updates, Google has focused heavily on promoting great content and natural links, and penalizing poor content with unspecified links.
C# 9.0 introduces record types, which are a reference type that provides synthesized methods to provide value semantics for equality. Records are immutable by default.
Record types make it easy to create immutable reference types in .NET. Historically, .NET types are largely classified as reference types (including classes and anonymous types) and value types (including structs and tuples). While immutable value types are recommended, mutable value types don’t often introduce errors. Value type variables hold the values so changes are made to a copy of the original data when value types are passed to methods.
There are many advantages to immutable reference types as well. These advantages are more pronounced in concurrent programs with shared data. Unfortunately, C# forced you to write quite a bit of extra code to create immutable reference types. Records provide a type declaration for an immutable reference type that uses value semantics for equality. The synthesized methods for equality and hash codes consider two records equal if their properties are all equal. Consider this definition:
The record definition creates a
Person type that contains two readonly properties:
Person type is a reference type. If you looked at the IL, it’s a class. It’s immutable in that none of the properties can be modified once it's been created. When you define a record type, the compiler synthesizes several other methods for you:
Person as follows:
You can also seal records to prevent further derivation:
The compiler synthesizes different versions of the methods above. The method signatures depend on if the record type is sealed and if the direct base class is object. Records should have the following capabilities:
Studentcan't be equal to a
Person, even if the two records share the same name.
In addition to the familiar
operator ==, and
operator !=, the compiler synthesizes a new
EqualityContract property. The property returns a
Type object that matches the type of the record. If the base type is
object, the property is
virtual. If the base type is another record type, the property is an
override. If the record type is
sealed, the property is
sealed. The synthesized
GetHashCode uses the
GetHashCode from all properties and fields declared in the base type and the record type. These synthesized methods enforce value-based equality throughout an inheritance hierarchy. That means a
Student will never be considered equal to a
Person with the same name. The types of the two records must match as well as all properties shared among the record types being equal.
The result of all these rules is the equality is implemented consistently across any hierarchy of record types. Two records are equal to each other if their properties are equal and their types are the same, as shown in the following example:
The compiler synthesizes two methods that support printed output: a ToString() override, and
PrintMembers takes a System.Text.StringBuilder as its argument. It appends a comma-separated list of property names and values for all properties in the record type.
PrintMembers calls the base implementation for any records derived from other records. The ToString() override returns the string produced by
PrintMembers, surrounded by
}. For example, the ToString() method for
Student returns a
string like the following code:
The examples shown so far use traditional syntax to declare properties. There's a more concise form called positional records. Here are the three record types defined earlier as positional records:
These declarations create the same functionality as the earlier version (with a couple extra features covered in the following section). These declarations end with a semicolon instead of brackets because these records don't add additional methods. You can add a body, and include any additional methods as well:
The compiler produces a
Deconstruct method for positional records. The
Deconstruct method has parameters that match the names of all public properties in the record type. The
Deconstruct method can be used to deconstruct the record into its component properties:
Finally, records support
with expressions. A
with expression instructs the compiler to create a copy of a record, but with specified properties modified:
The previous line creates a new
Person record where the
LastName property is a copy of
person, and the
"Paul". You can set any number of properties in a
with expression. You can also use
with expressions to create an exact copy. You specify the empty set for the properties to modify:
Any of the synthesized members except the "clone" method may be written by you. If a record type has a method that matches the signature of any synthesized method, the compiler doesn't synthesize that method. The earlier
Dog record example contains a hand coded ToString() method as an example.
Learn more about record types in this exploration of records tutorial.
Init only setters provide consistent syntax to initialize members of an object. Property initializers make it clear which value is setting which property. The downside is that those properties must be settable. Starting with C# 9.0, you can create
init accessors instead of
set accessors for properties and indexers. Callers can use property initializer syntax to set these values in creation expressions, but those properties are readonly once construction has completed. Init only setters provide a window to change state. That window closes when the construction phase ends. The construction phase effectively ends after all initialization, including property initializers and with-expressions have completed.
You can declare
init only setters in any type you write. For example, the following struct defines a weather observation structure:
Callers can use property initializer syntax to set the values, while still preserving the immutability:
But, changing an observation after initialization is an error by assigning to an init-only property outside of initialization:
Init only setters can be useful to set base class properties from derived classes. They can also set derived properties through helpers in a base class. Positional records declare properties using init only setters. Those setters are used in with-expressions. You can declare init only setters for any
struct you define.
Top-level statements remove unnecessary ceremony from many applications. Consider the canonical "Hello World!" program:
There’s only one line of code that does anything. With top-level statements, you can replace all that boilerplate with the
using statement and the single line that does the work:
If you wanted a one-line program, you could remove the
using directive and use the fully qualified type name:
Only one file in your application may use top-level statements. If the compiler finds top-level statements in multiple source files, it’s an error. It’s also an error if you combine top-level statements with a declared program entry point method, typically a
Main method. In a sense, you can think that one file contains the statements that would normally be in the
Main method of a
One of the most common uses for this feature is creating teaching materials. Beginner C# developers can write the canonical “Hello World!” in one or two lines of code. None of the extra ceremony is needed. However, seasoned developers will find many uses for this feature as well. Top-level statements enable a script-like experience for experimentation similar to what Jupyter notebooks provide. Top-level statements are great for small console programs and utilities. Azure Functions are an ideal use case for top-level statements.
Most importantly, top-level statements don't limit your application’s scope or complexity. Those statements can access or use any .NET class. They also don’t limit your use of command-line arguments or return values. Top-level statements can access an array of strings named args. If the top-level statements return an integer value, that value becomes the integer return code from a synthesized
Main method. The top-level statements may contain async expressions. In that case, the synthesized entry point returns a
C# 9 includes new pattern matching improvements:
andpatterns require both patterns to match
orpatterns require either pattern to match
notpatterns require that a pattern doesn’t match
These patterns enrich the syntax for patterns. Consider these examples:
Alternatively, with optional parentheses to make it clear that
and has higher precedence than
One of the most common uses is a new syntax for a null check:
Any of these patterns can be used in any context where patterns are allowed:
is pattern expressions,
switch expressions, nested patterns, and the pattern of a
Three new features improve support for native interop and low-level libraries that require high performance: native sized integers, function pointers, and omitting the
Native sized integers,
nuint, are integer types. They're expressed by the underlying types System.IntPtr and System.UIntPtr. The compiler surfaces additional conversions and operations for these types as native ints. Native sized integers define properties for
MinValue. These values can't be expressed as compile time constants because they depend on the native size of an integer on the target machine. Those values are readonly at runtime. You can use constant values for
nint in the range [
int.MaxValue]. You can use constant values for
nuint in the range [
uint.MaxValue]. The compiler performs constant folding for all unary and binary operators using the System.Int32 and System.UInt32 types. If the result doesn't fit in 32 bits, the operation is executed at runtime and isn't considered a constant. Native sized integers can increase performance in scenarios where integer math is used extensively and needs to have the fastest performance possible.
Function pointers provide an easy syntax to access the IL opcodes
calli. You can declare function pointers using new
delegate* syntax. A
delegate* type is a pointer type. Invoking the
delegate* type uses
calli, in contrast to a delegate that uses
callvirt on the
Invoke() method. Syntactically, the invocations are identical. Function pointer invocation uses the
managed calling convention. You add the
unmanaged keyword after the
delegate* syntax to declare that you want the
unmanaged calling convention. Other calling conventions can be specified using attributes on the
Finally, you can add the System.Runtime.CompilerServices.SkipLocalsInitAttribute to instruct the compiler not to emit the
localsinit flag. This flag instructs the CLR to zero-initialize all local variables. The
localsinit flag has been the default behavior for C# since 1.0. However, the extra zero-initialization may have measurable performance impact in some scenarios. In particular, when you use
stackalloc. In those cases, you can add the SkipLocalsInitAttribute. You may add it to a single method or property, or to a
interface, or even a module. This attribute doesn't affect
abstract methods; it affects the code generated for the implementation.
These features can improve performance in some scenarios. They should be used only after careful benchmarking both before and after adoption. Code involving native sized integers must be tested on multiple target platforms with different integer sizes. The other features require unsafe code.
Many of the other features help you write code more efficiently. In C# 9.0, you can omit the type in a
new expression when the created object's type is already known. The most common use is in field declarations:
new can also be used when you need to create a new object to pass as an argument to a method. Consider a
ForecastFor() method with the following signature:
You could call it as follows:
Another nice use for this feature is to combine it with init only properties to initialize a new object:
You can return an instance created by the default constructor using a
return new(); statement.
A similar feature improves the target type resolution of conditional expressions. With this change, the two expressions need not have an implicit conversion from one to the other, but may both have implicit conversions to a target type. You likely won’t notice this change. What you will notice is that some conditional expressions that previously required casts or wouldn’t compile now just work.
Starting in C# 9.0, you can add the
static modifier to lambda expressions or anonymous methods. Static lambda expressions are analogous to the
static local functions: a static lambda or anonymous method can't capture local variables or instance state. The
static modifier prevents accidentally capturing other variables.
Covariant return types provide flexibility for the return types of override methods. An override method can return a type derived from the return type of the overridden base method. This can be useful for records and for other types that support virtual clone or factory methods.
In addition, the
foreach loop will recognize and use an extension method
GetEnumerator that otherwise satisfies the
foreach pattern. This change means
foreach is consistent with other pattern-based constructions such as the async pattern, and pattern-based deconstruction. In practice, this change means you can add
foreach support to any type. You should limit its use to when enumerating an object makes sense in your design.
Next, you can use discards as parameters to lambda expressions. This convenience enables you to avoid naming the argument, and the compiler may avoid using it. You use the
_ for any argument. For more information, see the Input parameters of a lambda expression section of the Lambda expressions article.