添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Last time in our series about C# 7.3 language features, we covered tuple equality. Today we will look at unmanaged, delegate and enum type constraints . The latest Early Access Preview (EAP) versions of ReSharper 2018.2 and Rider 2018.2 come with language support for C# 7.3, do give them a try!

This post is part of a series:

  • Declaration expressions in initializers and queries
  • Tuple equality
  • Unmanaged, delegate and enum type constraints
  • Ref local re-assignment
  • Fixed pattern and simplified access to fixed size buffer elements
  • C# updates for stackalloc
  • Ever since C# 2.0, when generics were introduced, it has been possible to set type constraints on generic parameters . For example, we could specify that the type constraint should implement a specific interface, and perhaps also have a parameterless constructor:

    public class Repository<T>
        where T : IEntity, new()
        // ...
    

    Generic type constraints can be added on class declarations, method declarations, and local functions. They allow us to constrain the types which can be used with the class or method we are creating.

    Up until now, these constraints included reference type constraint (class), value type constraint (struct), interfaces or base class constraints, and whether a parameterless constructor should be present (new()). With C# 7.3, three new generic type constraints are introduced (proposal): unmanagedSystem.Delegate and System.Enum.

    (Fun fact: the CLR already supported these constraints, however the C# language prohibited using them. Jon Skeet has a blog post on making these constraints work in earlier C# versions.)

    The System.Enum constraint

    Let’s look at a simple example of the Enum constraint and write a method that returns the string representations of all values in an Enum. With the System.Enum generic type constraint, we can ensure this method only gets called with an Enum, and never with another type.

    public static IEnumerable<string> GetValues<T>()
        where T : struct, System.Enum
        var enumType = typeof(T);
        var items = Enum.GetValues(enumType);
        foreach (var item in items)
            yield return Enum.GetName(enumType, item);
    

    (Note we also added the struct, constraint here, to make sure T can’t be the System.Enum class itself – we want to prevent GetValues<System.Enum>() from being called!)

    The System.Delegate constraint

    Similarly, we can constrain generic classes and methods to System.Delegate now as well. All types this constraint is defined on must be the same. So combining two delegates of the same type (example 1) will work fine, combining two delegates with different types (example 2) will fail to compile:

    public static TDelegate Combine<TDelegate>(TDelegate source, TDelegate target)
        where TDelegate : Delegate
        return (TDelegate)Delegate.Combine(source, target);
    // Example 1
    void Hello() => Console.WriteLine("Hello");
    Action world = () => Console.WriteLine("World");
    var helloWorld = Combine(Hello, world);
    // Example 2
    Func<bool> test = () => true;
    var example = Combine(test, world);

    The unmanaged constraint

    While the unmanaged constraint will probably be used less, it does come in handy for some developers, typically when authoring low-level libraries and frameworks.

    In order to satisfy the unmanaged constraint, a type must be a struct and all the fields of the type must fall into one of the following categories:

  • Have the type sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, IntPtr or UIntPtr.
  • Be an enum type.
  • Be a pointer type.
  • Be a user-defined struct that satisfies the unmanaged constraint.
  • For example, when writing a class or method that only works with unmanaged types, we’d normally have to create overloads for every type. We can now use the unmanaged constraint instead, and then work with our value and declare pointers of unmanaged types, use the sizeof operator, allocate arrays on the stack, pin heap-allocated data using the fixed statement, and so on.

    private static unsafe void DoSomething<T>(T value) 
        where T : unmanaged
        // get size
        int size = sizeof(T);
        // get address and store it in pointer variable
        T* a = &value;
        // allocate array on stack
        T* arr = stackalloc T[42];
        // allocate array on heap and pin it
        fixed (T* p = new T[42])
            // ...
    

    When we call this method using a managed type (e.g. DoSomething("test")), the unmanaged constraint is not satisfied and code will not compile.

    ReSharper and Rider come with a code inspection and quick fix that suggests adding an unmanaged constraint. For example, when we try to declare a pointer to a managed type, code analysis will spot this and let us correct the issue using Alt+Enter.

    Now let’s add a bit of geekiness and have a look at the Intermediate Language (IL) that is emitted by the C# compiler (ReSharper | Tools | IL Code). The CLR itself has no notion of the unmanaged constraint – it only exists in C#.

    To make the constraint work, C# adds an attribute on our type parameter (System.Runtime.CompilerServices.IsUnmanagedAttribute), and emits a modreq (“required modifier”) of type ([mscorlib]System.Runtime.InteropServices.UnmanagedType) as well. By doing so, the compiler indicates that there are special semantics that should not be ignored. As such, compilers that do not emit this modreq will be unable to satisfy this constraint.

    It’s exciting to see some additions to the generic type system in .NET!

    Download ReSharper 2018.2 EAP now! Or give Rider 2018.2 EAP a try. We’d love to hear your feedback!

    .NET Core code analysis and quick-fixes ReSharper Rider visual studio
  • Share
  • Facebook
  • Twitter
  • Linkedin
  • Subscribe to a monthly digest curated from the .NET Tools blog:

    By submitting this form, I agree that JetBrains s.r.o. ("JetBrains") may use my name, email address, and location data to send me newsletters, including commercial communications, and to process my personal data for this purpose. I agree that JetBrains may process said data using third-party services for this purpose in accordance with the JetBrains Privacy Policy. I understand that I can revoke this consent at any time in my profile. In addition, an unsubscribe link is included in each email.

    Unreal Debugging Improvements in Rider 2024.2

    Rider 2024.2 includes massive improvements to the native debugger, significantly improving the experience of evaluating native code while debugging. There are performance improvements, better handling of optimised code, and support for evaluating operators on smart pointers and string types. The cha…

    Cast Expressions, Primary Constructors, Collection Expressions, List Patterns – C# Language Support in 2024.2

    Our release for ReSharper and Rider 2024.2 is just around the corner, and we have lots of exciting features shipping for the new C# 13 and current C# and VB.NET! Since there are so many, we will split them into multiple blog posts. So make sure to check those posts out as well! In this series, we…