Docs: C# 7.3: New forms of generic constraints unmanaged, System.Enum, and System.Delegate

Created on 15 Dec 2017  路  12Comments  路  Source: dotnet/docs

There are three new forms of generic constraints being introduced.

"blittable" for unmanaged types: dotnet/csharplang#187
"enum" to specify that the type must be derived from System.Enum dotnet/csharplang#104
"delegate" to specify that the type must derive from System.Delegate dotnet/csharplang#103

Reading the comments, a good part of the explanation will need to be explaining the blittable constraint and its name. There are a lot of subtleties to the types that can be members of a blittable type.

This will be new articles for the new constraint types, and updating general descriptions of generics where class and struct constraints are discussed.

Area - C# Guide P1

Most helpful comment

Blittable/unmanaged needs to happen! Maybe even contraints for operators and casts? Since operators cannot be added in an interface I cannot add 2 generic Variables together. Would be cool for a generic math library where we dont want a class for every single valuetype.

where T : blittable,
where T : ValueType (int, byte, long, etc. but no generic struct so we know it is a framework type)
where T : Castable to T1
where T : Operator+(T1,T2)

All 12 comments

Isn't "blittable" constraint called unmanaged, it would be good to lose "blittable" wording as it is not exact and confusing.

@nietras I create placeholder tasks based on proposals. I do update the titles and notes after the syntax is finalized and before I start writing the documentation. I'll update many of the 7.3 tasks early next week, when I go through all the updated proposals, many of which now have initial implementations.

Blittable/unmanaged needs to happen! Maybe even contraints for operators and casts? Since operators cannot be added in an interface I cannot add 2 generic Variables together. Would be cool for a generic math library where we dont want a class for every single valuetype.

where T : blittable,
where T : ValueType (int, byte, long, etc. but no generic struct so we know it is a framework type)
where T : Castable to T1
where T : Operator+(T1,T2)

The proposal has been updated and is posted here

There are three new constraints that are enabled by this feature:

  1. System.Enum is no longer disallowed as a base class constraint
  2. System.Delegate and System.MulticastDelegate are no longer disallowed as a base class constraint
  3. The new token unmanaged specifies the new "blittable" types constraint.

The following documents would need updates:

  • [x] [where generic type constraint](https://github.com/dotnet/docs/blob/master/docs/csharp/language-reference/keywords/where-generic-type-constraint.md): needs to include the unmanaged constraint.
  • [x] [Constraints on type parameters - C# Programming guide](https://github.com/dotnet/docs/blob/master/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md): needs to be updated for unmanaged
  • [x] [CS0702](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0702) needs to be updated to explain that System.Enum and System.Delegate can be used as base type constraints as of C# 7.3

Add samples:

  • [x] An extension method that enumerates all the values in an enum as strings.
  • [x] An extension method that invokes a delegate safely using the ?. operator
  • [x] A sample for an unmanaged type that converts the type to an array of bytes for transmission on a wire.

A good location for these samples is the conceptual topic for generic type constraints

Closed in #4960

Is enum still a valid constraint? If not, where T : System.Enum allows T to be any enum, but also System.Enum, which is usually not what you would want. I suppose where T : struct, Enum will work then.

@IllidanS4 where T : System.Enum would allow T to be System.Enum, but System.Enum is an abstract class. So, at runtime, T would be an enum type, not System.Enum. You could specify System.Enum as the type parameter, but any instantiated object would be an enum. Do you see scenarios where that could make a difference?

@BillWagner I don't understand the argument; at runtime, T would be exactly System.Enum and objects of type T will be boxed enum values. The difference would be in potential null values, which programmers would need to check, causing more confusion. And what about this?

public static string Method<T>(T? arg) where T : Enum
{
    return arg?.ToString();
}

That cannot be expressed right now. If you wanted to pass null, you would have to make it Enum arg at the expense of boxed objects, but then the type safety would have to be handled at runtime. At the moment, constraining T to a concrete enum type would be where T : Enum, new(), but the compiler then must deduce that such type can only be a value type, which makes things more complicated than necessary.

Imagine if instead of where T : struct, you would have to write where T : ValueType. Wouldn't that bring all sorts of problems, confusions and difficulties? (By the way, I hope where T : ValueType will be possible too, just for the sake of consistency.)

Thanks for adding more to your comment @IllidanS4 I think I understand your comments better.

First, if you want to propose new language features, I'd write those issues on the dotnet/csharplang repo. The language designers watch that much more closely.

Here, I'll address where I might need to add more clarification:

You said:

at runtime, T would be exactly System.Enum and objects of type T will be boxed enum values.

No, that's not correct. The last sentence of the spec covering generic type parameters says "The run-time execution of all statements and expressions involving type parameters uses the actual type that was supplied as the type argument for that parameter." Therefore, T is replaced with its actual type. If T is an enum, the runtime code uses that enum type, not System.Enum. That means no boxing.

As for your example, this would work for excluding System.Enum as the type:

public static string Method<T>(T? arg) where T : struct, Enum
{
    return arg?.ToString();
}

You could create overloads:

public static string Method<T>(T? arg) where T : struct, Enum
{
    return arg?.ToString();
}
public static string Method(Enum arg)
{
    return arg?.ToString();
}

Overload resolution ensures that the generic method would be better than the non-generic method for any enum type.

/cc @agocke to validate my explanation.

@BillWagner I still might have not explained myself properly. T will of course be replaced with the actual type you supply and not the base type, but my point is that the user could supply actual System.Enum as T, which is, to my knowledge, possible.

However, seeing you use where T : struct, Enum, I see this is also possible now, so that's all what I wanted to know (where T : struct, Type was not possible before). I just hope people will not use where T : Enum when they want where T : struct, Enum. Perhaps where T : enum would still be more clear.

A similar issue is in the delegate constraint. where T : Delegate still allows T to be System.Delegate or System.MulticastDelegate, even though some programmers would expect it to allow concrete types only. Perhaps a new "non-abstract" constraint might be useful.

I think both of what you said is correct, depending on the precise meaning of your terms. The simplest explanation I can come up with is T : System.Enum means exactly what it says and no more -- the type substitution for T must inherit from System.Enum or itself be System.Enum. System.Enum cannot be created directly, but it could be a type argument and enums would be boxed during conversion.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

skylerberg picture skylerberg  路  3Comments

sime3000 picture sime3000  路  3Comments

LJ9999 picture LJ9999  路  3Comments

stjepan picture stjepan  路  3Comments

svick picture svick  路  3Comments