Location>code7788 >text

Improving Productivity in C#: The Complete Guide to C# 13 Updates

Popularity:168 ℃/2024-07-27 15:42:48

preamble

It is expected that in November 2024, C# 13 will be officially released along with .NET 9. This year's C# updates focus onref struct Many improvements have been made and many handy features have been added to help further increase productivity.

This article describes features that are expected to be added to C# 13.

Note: C# 13 is not yet officially released, so the following is subject to change.

Use in iterators and asynchronous methodsref cap (a poem)ref struct

When programming in C#, do you often use theref variables andSpan et al. (and other authors)ref struct types? However, these can't be used in iterators and asynchronous methods, and so localized functions and the like must be used to avoid using them directly in iterators and asynchronous methodsref variantref struct type, which is very inconvenient.

This shortcoming was ameliorated in C# 13, where iterators and asynchronous methods can now also use theref cap (a poem)ref struct Up!

In iterators, use theref cap (a poem)ref struct The example of the

IEnumerable<float> GetFloatNumberFromIntArray(int[] array)
{
    for (int i = 0; i < ; i++)
    {
        Span<int> span = ();
        // Do some processing....
        ref float v = ref <int, float>(ref array[i]);
        yield return v;
    }
}

In asynchronous methods using theref struct The example of the

async Task ProcessDataAsync(int[] array)
{
    Span<int> span = ();
    // Do some processing....
    ref int element = ref span[42];
    element++;
    await ();
}

I've used the inappropriate and ambiguous "some processing" in order to demonstrate the functionality, but the important thing is that it's now possible to use theref cap (a poem)ref struct Up!

There is, however, one caveat.ref variables andref struct Variables of type can't go beyond theyield cap (a poem)await boundaries are used. For example, the following example will result in a compilation error.

async Task ProcessDataAsync(int[] array)
{
    Span<int> span = ();
    // Do some processing...
    ref int element = ref span[42];
    element++; await (); // Do some processing...
    await (); element++; // Error.
    element++; // Error: access to element is out of bounds for await
}

Although we've talked about it, I think there may be some people who are wondering what the hellref cap (a poem)ref struct What it is, so I'll explain a little bit.

In C#, you can use theref to get a reference to the variable. This allows the original variable to be changed by reference. The following is an example:

void Swap(ref int a, ref int b) // ref denotes a reference
int temp = a; ref
    int temp = a; a = b; ref
    a = b; b = temp; // Here, a and b have been swapped.
    b = temp; // Here, a and b have been swapped.
}

int x = 1; int y = 2; // here a and b have been exchanged }
int y = 2; Swap(ref x, ref y); // Get references to x and y.
Swap(ref x, ref y); // get references to x and y, call Swap to swap x and y

On the other hand.ref struct is used to define value types that can only exist on the stack. This is to avoid the overhead of garbage collection. However, since theref struct can only exist on the stack, so prior to C# 13 it couldn't be used in places like iterators and asynchronous methods.

By the way.ref struct The reason why it comes withrefThis is becauseref struct instances can only exist on the stack, which follows the same life cycle rules as theref The variables are identical.

allows ref struct generalized constraint

In the old days.ref struct cannot be used as a generic type parameter, so a generic type was introduced with code reusability in mind, but ultimatelyref struct Cannot be used, must beSpan maybeReadOnlySpan Rewriting the same processing was then cumbersome.

In C# 13, generic types can also be used with theref struct Up:

using System;
using ;

Process([1, 2, 3, 4], Sum); // 10
Process([1, 2, 3, 4], Multiply); // 24

T Process<T>(ReadOnlySpan<T> span, Func<ReadOnlySpan<T>, T> method)
{
    return method(span);
}

T Sum<T>(ReadOnlySpan<T> span) where T : INumberBase<T>
{
    T result = ;
    foreach (T value in span)
    {
        result += value;
    }
    return result;
}

T Multiply<T>(ReadOnlySpan<T> span) where T : INumberBase<T>
{
    T result = ;
    foreach (T value in span)
    {
        result *= value;
    }
    return result;
}

Why is something likeReadOnlySpan<T> this kind ofref struct The type can be used as aFunc What about the type parameter of the . To investigate this, I looked at the .source code (computing)DiscoverFunc The generic parameters of a type are defined like this:

public delegate TResult Func<in T, out TResult>(T arg)
    where T : allows ref struct
    where TResult : allows ref struct;

If you add a generic parameter to theallow ref struct constraints, then theref struct The type is passed to this parameter.

It's really a handy feature.

ref struct You can also implement the interface

In C# 13.ref struct Interfaces can be implemented.

If this feature is combined with theallows ref struct In combination, then references can also be passed through generic types:

using System;
using ;

int a = 10;
// utilization Ref<int> save (a file etc) (computing) a references
Ref<int> aRef = new Ref<int>(ref a);
// pass on to sb else Ref<int>
Increase<Ref<int>, int>(aRef);
(a); // 11

void Increase<T, U>(T data) where T : IRef<U>, allows ref struct where U : INumberBase<U>
{
    ref U value = ref ();
    value++;
}

interface IRef<T>
{
    ref T GetRef();
}

// because of Ref<T> this kind of ref struct implementation interface
ref struct Ref<T> : IRef<T>
{
    private ref T _value;

    public Ref(ref T value)
    {
        _value = ref value;
    }

    public ref T GetRef()
    {
        return ref _value;
    }
}

In this way, writingref struct related code becomes much easier. Also, it is possible to give variousref struct Implemented enumerator implementationsIEnumerator and such interfaces too.

The set type andSpan It is also possible to use theparams

In the old days.params can only be used for array types, but as of C# 13, it can also be used for other collection types andSpan

params is a function that allows any number of parameters to be specified directly when calling a method.

For example.

Test(1, 2, 3, 4, 5, 6);
void Test(params int[] values) { }

As shown above, it is straightforward to specify any number ofint Parameters.

As of C# 13, collection types other than array types,SpanReadOnlySpan types and interfaces related to collections can also be addedparams

Test(1, 2, 3, 4, 5, 6);
void Test(params ReadOnlySpan<int> values) { }

// or
Test(1, 2, 3, 4, 5, 6);
void Test(params List<int> values) { }

// The interface can also
Test(1, 2, 3, 4, 5, 6);
void Test(params IEnumerable<int> values) { }

That's handy, too!

field Keywords.

When implementing properties in C#, it is often necessary to define a bunch of fields as follows...

partial class ViewModel : INotifyPropertyChanged
{
    // Defining Fields
    private int _myProperty;

    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty != value)
            {
                _myProperty = value;
                OnPropertyChanged();
            }
        }
    }
}

So, starting with C# 13.field Keywords will come in handy!

partial class ViewModel : INotifyPropertyChanged
{
    public int MyProperty
    {
        // Simply use the field
        get => field;
        set
        {
            if (field != value)
            {
                field = value;
                OnPropertyChanged();
            }
        }
    }
}

No more defining your own fields, just use thefield keyword, the field is automatically generated.

This is also very convenient!

Partial properties

One of the common problems when writing C# is that properties cannot be added to thepartial Modifiers.

In C#, you can add a class or method to thepartialin order to declare and implement them separately. In addition, it is possible to decentralize the parts of the class. Its main use is to specify what to generate when using automatic generation tools such as source code generators.

Example:

partial class ViewModel
{
    // Only methods are declared here, the implementation is automatically generated by the tool.
    partial void OnPropertyChanged(string propertyName); }
}

The autogeneration tool then generates the following code:

partial class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    partial void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new(propertyName));
    }
}

The developer only needs to declare theOnPropertyChangedThe implementation will be fully automated, thus saving the developer's time.

Starting with C# 13, attributes also supportpartial

partial class ViewModel
{
    // Declaring Partial Properties
    public partial int MyProperty { get; set; }
}

partial class ViewModel
{
    // Implementation of some of the attributes
    public partial int MyProperty
    {
        get
        {
            // ...
        }
        set
        {
            // ...
        }
    }
}

In this way, attributes can also be automatically generated by the tool.

lock object (computing)

As we all know.lock is a function that is used for thread synchronization via a monitor.

object lockObject = new object();
lock (lockObject)
{
    // critical zone
}

However, the overhead of this feature is actually quite high and can affect performance.

To solve this problem, C# 13 implements a lock object. To use this feature, simply use the Just replace the locked object:

using ;

Lock lockObject = new Lock();
lock (lockObject)
{
    // critical zone
}

This makes it easy to improve performance.

Tail index in the initializer

indexing operator^ can be used to represent the relative position of the end of a collection. Starting with C# 13, initializers also support this feature:

var x = new Numbers
{
    Values =
    {
        [1] = 111, [^1] = 999 // ^1 is the first element from the end.
        [^1] = 999 // ^1 is the first element from the end
    }
    // [1] is 111
    // [9] is 999 because Values[9] is the last element
};

class Numbers
class Numbers {
    public int[] Values { get; set; } = new int[10]; }
}

ESCAPE character

In Unicode strings, you can use\e substitute (X for Y, or a number in an algebraic expression)\u001b cap (a poem)\x1b\u001b\x1b cap (a poem)\e Both represent the ESCAPE character. They are usually used to represent control characters.

  • \u001b denotes a Unicode escape sequence.\u The last 4 hexadecimal digits represent the Unicode code point.
  • \x1b represents a hexadecimal escape sequence.\x The last 2 hexadecimal digits represent the ASCII code.
  • \e Indicates the ESCAPE character itself

Recommended\e The reason for this is that confusion in hexadecimal can be avoided.

For example, if\x1b Followed by.3is changed to\x1b3As a result of\x1b cap (a poem)3 There is no clear separation between them, so it is not clear that they should be interpreted as separate\x1b cap (a poem)3, or put together and explained.

If you use the\eInstead, confusion can be avoided.

(sth. or sb) else

In addition to the above features, there are some improvements to natural types in method groups and prioritization in method overloading, but they are omitted from this article. If you want to know more, please refer to the documentation.

concluding remarks

C# is evolving year after year, and for me the C# 13 update has implemented a lot of very useful and handy features that solve a lot of real pain points. Looking forward to the official release of .NET 9 and C# 13!