C# 7.3 upgrades can be categorized into these:
The -publicsign
compiler option instructs the compiler to sign the assembly using a public key
The -pathmap
compiler option instructs the compiler to replace source paths from the build environment with mapped source paths.
The fixed statement prevents the garbage collector from relocating a movable variable. The fixed statement is only permitted in an unsafe context. fixed can also be used to create fixed size buffers.
Starting with C# 7.3, the fixed statement operates on additional types beyond arrays, strings, fixed-size buffers, or unmanaged variables.
Any type that implements a method named GetPinnableReference can be pinned. The GetPinnableReference must return a ref variable to an unmanaged type.
Before
unsafe struct S
{
public fixed int myFixedField[10];
}
class C
{
static S s = new S();
unsafe public void M()
{
fixed (int* ptr = s.myFixedField)
{
int p = ptr[5];
}
}
}
In earlier versions of C#, you need to declare a second fixed pointer
After
unsafe struct S
{
public fixed int myFixedField[10];
}
class C
{
static S s = new S();
unsafe public void M()
{
int p = s.myFixedField[5];
}
}
Without pinning the variable p
inside a separate fixed
statement. You don't need to declare a separate int* variable.
ref
locals may be reassigned to refer to different instances after being initialized. The following code now compiles
ref VeryLargeStruct refLocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.
The stackalloc keyword is used in an unsafe code context to allocate a block of memory on the stack.
Beginning with C# 7.3, you can use array initializer syntax for stackalloc arrays. All the following declarations declare an array with three elements whose values are the integers 1, 2, and 3
// Valid starting with C# 7.3
int* first = stackalloc int[3] { 1, 2, 3 };
int* second = stackalloc int[] { 1, 2, 3 };
int* third = stackalloc[] { 1, 2, 3 };
The use of stackalloc automatically enables buffer overrun detection features in the common language runtime (CLR). If a buffer overrun is detected, the process is terminated as quickly as possible to minimize the chance that malicious code is executed.
You can now specify the type System.Enum or System.Delegate as base class constraints for a type parameter.
You can also use the new unmanaged constraint, to specify that a type parameter must be an unmanaged type. An unmanaged type is a type that isn't a reference type and doesn't contain any reference type at any level of nesting.
These operators work by comparing each member of the left argument to each member of the right argument in order. These comparisons short-circuit. They will stop evaluating members as soon as one pair is not equal.
var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // displays 'true'
// lifted conversions
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true
// converted type of left is (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Also true
// comparisons performed on (long, long) tuples
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Also true
(int a, string b) pair = (1, "Hello");
(int z, string y) another = (1, "Hello");
Console.WriteLine(pair == another); // true. Member names don't participate.
Console.WriteLine(pair == (z: 1, y: "Hello")); // warning: literal contains different member names
//
nested tuples
(int, (int, int)) nestedTuple = (1, (2, 3));
Console.WriteLine(nestedTuple == (1, (2, 3)) );
When the in argument modifier was added, these two methods would cause an ambiguity:
static void M(S arg);
static void M(in S arg);
To call the version with the readonly reference argument, you must include the in modifier when calling the method.
The syntax added in C# 7.0 to allow out variable declarations has been extended to include field initializers, property initializers, constructor initializers, and query clauses. It enables code such as the following example
public class B
{
public B(int i, out int j)
{
j = i;
}
}
public class D : B
{
public D(int i) : base(i, out var j)
{
Console.WriteLine($"The value of 'j' is {j}");
}
}