-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support System.Enum as generic type constraint. #306
Comments
Hey @ericmutta , @KathleenDollard would be the person to say what the current priority is but as for what discussions we had on it it was uncontroversial. I'm assuming VB can consume APIs which have this constraint from C# but may still need the restriction lifted on defining them to support overriding. I imagine the C# implementation could be ported easily by a community member. I think @KlausLoeffelmann expressed an interest here. |
Thanks @AnthonyDGreen for the prompt reply! We are half way through the year, so fingers crossed at least one of the ideas we've all been discussing in this repo will make it into the language this year! 👍 |
Do you have a stronger case than creating a dictionary from an enum for this? (which can be done in other ways). This feature is so limited in C# that I'm not sure where the value its. I'd love to see a case made. |
Just tagging in some related issues in other repos that I've just discovered: csharplang: Champion "Allow |
Hi Kathleen, good to see you back online! 😃 If I am being honest, I copied the C# dictionary code more as a cop-out in the vein "look the C# guys did it so, clearly this is both possible and awesome so let's do it" kinda thing, because it was shorter that way. Here is the longer version and an attempt to make a case with an example from a real project that I am working on:
The lack of an enum type constraint makes number 5 above (i.e the generic features such as logging) rather awkward to implement. Concrete example: Let's say your app has two services: ServiceA and ServiceB. The request and response codes for ServiceA are defined using an enum called EnumA and those for ServiceB use an enum called EnumB. Note that EnumA and EnumB are considered two different types, so if you wanted to write any function that can take values from both enums, that function would need to use generics, and the problem is there no way to enforce the constraint that the type of values that the function expects must support the enum property of "numeric values with an associated name". That property is required if the function for example, counts the number of times each request/response occurs then prints a summary table in a developer dashboard showing the results using more friendly request/response names instead of the opaque numeric codes. Ultimately, the uses for this feature are limited only by a developer's imagination and it just seems incomplete to have the language support other type constraints but not this one (especially since there's been a lot of developer interest in this for a long time as @reduckted has handily shown by tagging in other issues that touch on it). Here's hoping the long version actually helped matters rather than making them worse 🤞 |
It's actually the code case I'd like to see. I think people overestimate what this feature does. In your case, you have to cast to the specific enum type. What is the code that is better with the enum constraint than without it? And this isn't an service on the edge as the infrastructure (ASP.NET) needs the specific type. I'm happy to just have a link to a real world scenario where the C# code is fundamentally better with the constraint-beyond the dictionary example which I agree is better, but limited usage. For the case you mention, enum alternatives would work great. |
OK here is an example of code (using the request-response protocol scenario I mentioned earlier) that wont even compile because we can't constrain generic types to an enum (comments are inline since its quite lengthy): Public Enum EFooUploadServiceCodes
UploadRequest
UploadResponse_OK
UploadResponse_Denied
End Enum
Public Enum EFooDownloadServiceCodes
DownloadRequest
DownloadResponse_OK
DownloadResponse_Denied
End Enum
Public Class CRequestCounter(Of TEnum As Structure)
'this is an array because we want O(1) lookup performance and compact memory representation
'to help ensure better cache behaviour. Counting requests should be really fast to prevent
'delays in response times.
Private mRequestCounters As Integer()
Public Sub New()
'initialise the request counters array to have as many elements as there are members in
'the enum. NOTE: this line WILL FAIL AT RUN-TIME if you pass in an actual structure type
'such as System.DateTime.
Me.mRequestCounters = New Integer([Enum].GetValues(GetType(TEnum)).Length - 1) {}
End Sub
Public Sub CountRequestResponse(ArgCode As TEnum)
'increment the count for request/response with code given in parameter ArgCode.
'NOTE: this line DOESN'T EVEN COMPILE because compiler doesn't know TEnum will be
'an enum type (the 'Structure' type constraint doesn't allow us to express this statically).
Me.mRequestCounters(ArgCode) += 1
'the above code WOULD compile if we could constrain types to System.Enum
'because you CAN index into arrays using enum members since the compiler
'DOES KNOW that they have an underlying numeric value as shown
'in the lines below which use two different enum types.
Me.mRequestCounters(EFooUploadServiceCodes.UploadRequest) += 1
Me.mRequestCounters(EFooDownloadServiceCodes.DownloadRequest) += 1
End Sub
End Class While I haven't looked at exactly what the C# implementation allows, what I HOPE will be possible in VB is to write this: Public Class CRequestCounter(Of TEnum As Enum) '<--- constrain TEnum to be any enum type
End Class So that within Me.mRequestCounters(ArgCode) += 1 ...which wouldn't even compile in the code given earlier because not all types that conform to the
I would like to suggest that the "limited usage" argument should NOT be the main reason for rejecting this frequently requested/discussed feature. The VB language has many things with limited/infrequent usage that are still very handy to have (example: operator overloading is an advanced feature that you can go YEARS without ever using, but it is there, because when you need it, you really really need it to avoid awkward alternatives). |
@ericmutta Good example, but it looks like that won't even work in C# 7.3. 😢 Here's what I tried: enum EFooUploadServiceCodes
{
UploadRequest,
UploadResponse_OK,
UploadResponse_Denied
}
class RequestCounter<TEnum> where TEnum : Enum
{
private int[] mRequestCounters;
RequestCounter()
{
mRequestCounters = new int[Enum.GetValues(typeof(TEnum)).Length];
}
void CountRequestResponse(TEnum argCode)
{
// CS0029: Cannot implicitly convert type 'TEnum' to 'int'
mRequestCounters[argCode] += 1;
~~~~~~~~~~~~~~~~~~~~~~~~~
// CS0030: Cannot convert type 'TEnum' to 'int'
mRequestCounters[(int)argCode] += 1;
~~~~~~~~~~~~
// CS0266: Cannot implicitly convert type 'EFooDownloadServiceCodes' to 'int'. An explicit conversion exists (are you missing a cast?)
mRequestCounters[EFooUploadServiceCodes.UploadRequest] += 1;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Casting to an object, then to an int works, but it ends up boxing the value.
mRequestCounters[(int)(object)argCode] += 1;
// Explicitly casting an actual enum value to an int works.
mRequestCounters[(int)EFooUploadServiceCodes.UploadRequest] += 1;
}
} |
Thanks for trying it out to confirm the C# behaviour! 👏 👏 I am beginning to regret making the C# reference 😞 and I hope we will (re)consider this feature afresh for VB, following VB's semantics for enums (e.g. the ability to index into an array using an enum value directly which, for reasons that escape me, C# doesn't allow unless you use an explicit cast, as @reduckted's code above shows). |
I haven't looked at how it's implemented in C#, but I suspect it doesn't work as we'd like because the type constraint is |
This is what I've been using so far: ' String Enums, i.e. DescriptionAttribute
<Extension>
Public Function GetDescription(obj As [Enum]) As String
' IEnumerable-style for Enums
Public Shared Function AsEnumerable(Of T As TEnum)() As T()
Return DirectCast([Enum].GetValues(GetType(T)), T())
End Function
Public Shared Function Count(Of T As TEnum)() As Integer
Return [Enum].GetValues(GetType(T)).Length
End Function
Public Shared Function Min(Of T As TEnum)() As T
Return AsEnumerable(Of T).First()
End Function
Public Shared Function Max(Of T As TEnum)() As T
Return AsEnumerable(Of T).Last()
End Function
' helpers regarding FlagsAttribute
<Extension>
Public Function Flags(Of T As TEnum)([enum] As T) As IEnumerable(Of T)
<Extension>
Public Function HasFlag(Of T As TEnum)([enum] As T, flag As Byte) As Boolean
Return (System.Convert.ToByte([enum]) And flag) = flag
End Function
<Extension>
Public Function HasFlag(Of T As TEnum)([enum] As T, flag As Integer) As Boolean
Return (System.Convert.ToInt32([enum]) And flag) = flag
End Function Note that you actually "can" put Enum as generic constraint somehow, see http://stackoverflow.com/a/1416660 |
If anyone's interested, here's the pull request for C#: dotnet/roslyn#24199 |
Thanks for digging that up! It's interesting to note that in that pull request, on two seperate occassions (first by @VSadov and then by @AlekseyTs) there was mention of doing the same thing for VB. Ignoring the C# implementation for a moment (I don't want what they did there to limit us here), it would be great to get a VB implementation where:
Below are some example operations that should be valid for V since they are legal when used directly with known enum member values: Public Module Module1
Public Enum EFoo
Foo1
Foo2
End Enum
Public Enum EBar
Bar1
Bar2
End Enum
Public Sub Main()
Dim SomeArray = {1, 2, 3}
'indexing into array using enum member.
SomeArray(EFoo.Foo1) = 12
SomeArray(EBar.Bar1) = 15
'comparing against integers
If EFoo.Foo1 > 12 Then Stop
'comparing against other members in same enum.
If EFoo.Foo1 < EFoo.Foo2 Then Stop
'comparing against other enums
If EBar.Bar1 = EFoo.Foo1 Then Stop
'doing arithmetic with enum members.
Dim sum = EBar.Bar1 + EFoo.Foo2
'assigning to numerically typed variables.
Dim number As Integer = EFoo.Foo1
End Sub
End Module I think the above rough spec covers most scenarios, but hope others can add to it in case I forgot something. Some pending considerations:
|
With Option Strict On does "If EFoo.Foo1 > 12 Then Stop" require a Cast on 12? What about comparing against other enums. The specific case I care about is working with SyntaxKind where there are 3 different Enums, VB(VB only), CSharp(C# Only) and Raw (both Lists combined with overlap for a few values). I find myself using Raw a lot but then I lose the better debugging experience that I get with the language specific Enum like debugger displaying a friendly name. |
Neither of those cases require explicit casting and they never should (in all cases you are comparing integers which is an operation that cannot fail unless cosmic rays don't like you). VB does the sane thing here, it just works! 👍 |
I agree that the Enum constraint would allow protection of sending a type that wasn't an Enum into the methods described by @ericmutta and @hartmair. The Enum type does not know the underlying type is an int (and it may not be), so there really isn't very much you can with this constraint, other than the protection. But this protection will be a little scattered as the current implementation of the Enum methods themselves do not allow compile time checks. I don't think this is a bad idea, I'm just not sure it's more important than other things. It only provides the protection, and nothing else. And I'm not convinced the underlying fundamentals of Enum will allow the any of the requests in @ericmutta 's list. |
It does provide the protection (so you CAN'T pass in types like Public Sub CountRequestResponse(ArgCode As TEnum)
'increment the count for request/response with code given in parameter ArgCode.
'NOTE: this line DOESN'T EVEN COMPILE because compiler doesn't know TEnum will be
'an enum type (the 'Structure' type constraint doesn't allow us to express this statically).
Me.mRequestCounters(ArgCode) += 1
'the above code WOULD compile if we could constrain types to System.Enum
'because you CAN index into arrays using enum members since the compiler
'DOES KNOW that they have an underlying numeric value as shown
'in the lines below which use two different enum types.
Me.mRequestCounters(EFooUploadServiceCodes.UploadRequest) += 1
Me.mRequestCounters(EFooDownloadServiceCodes.DownloadRequest) += 1
End Sub In the code above if
For any given feature request X, there is always going to be a feature request Y that is more important (for example I would throw this request out in a heartbeat if I heard your team was working on #238 instead). Having given both real-world scenarios and code examples, I don't know what else to do! Could we at least agree to either have it rejected so the issue can be put to rest or accepted for future implementation when more pressing issues have been handled? It would certainly help everyone to know that this is either never gonna happen or will happen "soon" even if soon refers to some undefined future period (in the Rosyln repo they have a milestone called |
The I have implemented a faster and safer replacement of |
That would give incorrect results for negative-valued enum constants. |
Yes. But luckily those incorrect results are still useful in some bitwise algorithms, such as |
Just ran into this on our project. We have a c# library that is consumed by a vb.net application. The C# library has a type with the following signature:
The vb.net application has a function that returns this open generic type which cannot be defined:
So not having this feature kind of breaks using C# libraries from vb.net. |
It would be great to have the same level of support in VB for enum constraints that we have in C#. Marked as Approved. |
@KathleenDollard many thanks to you and the team for giving this consideration! |
The original @ericmutta requirement is already available in B# I thought. |
The code you linked to uses a specific enum type (i.e PS: what on earth is B#? 😕 |
Got your point. If full support of enum comes in, it will be par with python where we can use the enum.range in a FOR loop. |
May I bring your attention on this example of Java enum:
This is basically a Dictionary(of Enum, String) and it can be translated to B# as:
which is definitely an overkill compared to Java syntax. |
I don't know if (sane) people out there choose a language based on it's alphabetical sort position, but since this name change is never going to happen, I think it is best we avoid the B# reference - it only confuses things! |
Welcome aboard! We are a friendly bunch here and it is always nice to see new faces 😃
This looks interesting, could you post it in a seperate issue so it can get its own dedicated discussion? For example, given my limited knowledge of Java, I would like to know what the data type of |
@ericmutta |
Well, my scenario is that I want to do a no value check, which means I need to be able to cast the enum to an long without boxing. You can't currently do that with a System.Enum, but you can for a specific type of enum. Apparently this can be done with a single IL opcode, Conv_I8, but for some reason that isn't supported for System.Enum. Basically I would want to be able to cast to a integer/long from within a constrained method. |
I was just looking for this kind of thing in VB.Net and was sad to find that C# has this (mostly as they cannot convert to the base data type without boxing - int, uint, etc.), but VB.Net does not. |
It would be awesome if VB.NET had this. I have Enum extensions methods that I want to constrain to only allow Enums. Hopefully Microsoft knows there are many codebases out there that still rely heavily on VB.NET, and want these updates. Of course I get that it is not a priority as the majority of devs use C#, but it would be nice to get more C# features. |
There is some work being done to avoid ambiguity in type matching in calling functions this might be something that could help. Most improvement in VB outside of IDE improvements are driven by changes in C# that break compatibility with VB or a use case where VB is broken. It’s possible that an optimized solution could be built with source generators.Sent from my iPhoneI apologize for any typos Siri might have made. On Mar 24, 2024, at 11:07 PM, Nicholas Neville ***@***.***> wrote:
It would be awesome if VB.NET had this. I have Enum extensions methods that I want to constrain to only allow Enums.
Too bad that this is the only fallback as of writing.
Hopefully Microsoft knows there are many codebases out there that still rely heavily on VB.NET, and want these updates.
Of course I get that it is not a priority as the majority of devs use C#, but it would be nice to get more C# features.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
|
This concept has been discussed for years on the web and I just discovered that C# 7.3 now supports it:
@AnthonyDGreen would you happen to know what the discussion was for supporting this in VB as well when it was planned for C#?
The text was updated successfully, but these errors were encountered: