-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
API Proposal: Add a Generic version of GetValues to Enum (probably GetName/GetNames) #2364
Comments
Split off from dotnet/corefx#15453 Enums are essential commonly used types but have several areas in need of improvement. For each non-generic Rationale and Usage
With this proposal implemented what used to be this to validate a standard enum value MyEnum value = ???;
bool isValid = Enum.IsDefined(typeof(MyEnum), value); now becomes this MyEnum value = ???;
bool isValid = value.IsDefined(); With this implemented it will address dotnet/corefx#10692. Proposed APInamespace System {
public abstract class Enum : ValueType, IComparable, IConvertible, IFormattable {
public static string Format<TEnum>(TEnum value, string format) where TEnum : struct, Enum;
public static string GetName<TEnum>(this TEnum value) where TEnum : struct, Enum;
public static IReadOnlyList<string> GetNames<TEnum>() where TEnum : struct, Enum;
public static IReadOnlyList<TEnum> GetValues<TEnum>() where TEnum : struct, Enum;
public static bool IsDefined<TEnum>(this TEnum value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(object value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(sbyte value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(byte value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(short value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(ushort value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(int value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(uint value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(long value) where TEnum : struct, Enum;
public static TEnum ToObject<TEnum>(ulong value) where TEnum : struct, Enum;
}
} API DetailsThis proposal makes use of a This proposal specifies extension methods within Alternatively, the extension methods could be defined in a separate static Open Questions
Updates
|
This looks like a separate proposal. Let's keep this focused on just this API: namespace System
{
public partial abstract class Enum
{
public static TEnum[] GetValues<TEnum>() where TEnum : Enum;
}
} This looks reasonable and we agree that it makes calling code nicer. @tannergooding brought up runtime concerns around polluting generic instantiations. @jkotas what are your thoughts on this? |
Right, just want to confirm that there isn't any concern around adding a generic API to a non-generic type. IIRC, there was concerns raised around that in the past. |
I've moved those proposed API's back to dotnet/corefx#15453. I'm especially opposed to returning an array for |
We do have a number of non-generic methods in the surface. How are we deciding when it is worth creating duplicate generic wrapper to save a bit of typing, e.g. why not have generic versions of If
Does it really matter? |
It's common to use enums to represent options in a dropdown and if |
It will be a single allocation (the array) that is relatively small overall (even for the full country list, you are looking at under 2kb for the array, if you assume ~200 countries and 8 bytes per entry). |
The cost of the allocation could also be amortized by caching on the UX side or be utilizing an array pool. |
That's true. If a server were to do the caching that would reduce the allocation cost. I'd still prefer it to return an |
@tannergooding How do you use an array pool here, when the implementation of |
FWIW, from my benchmarking in dotnet/coreclr#22161 the current implementation of the non-generic version of this method allocates much more than would be expected, about 1kb for an enum with 36 members. |
The lack of convenient enum reflection APIs has been long-standing. At a minimum, there should be a maximally convenient API for all common enum reflection scenarios. If such a convenient API does not have good performance it might make sense to add a high-performance API in some form. But callers can already ensure optimal performance by doing caching work themselves. IMO, the most important use case, that is not currently served, is a convenient API. I have seen many enum tickets on this issue tracker. I think it would be best if all of them were looked at together and a holistic solution was implemented. |
I stand corrected, we believe we'd prefer this API shape: namespace System
{
public partial abstract class Enum
{
public static TEnum[] GetValues<TEnum>() where TEnum : struct, Enum;
public static string GetName<TEnum>(TEnum value) where TEnum : struct, Enum;
public static string[] GetNames<TEnum>() where TEnum : struct, Enum;
public static bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum;
public static bool HasFlag<TEnum>(TEnum value, TEnum test) where TEnum : struct, Enum;
}
} |
Please consider adding |
Hmm, not sure it's worth it. |
Granted, most users will only be checking one enum flag value and as such the semantics between However, consider a [Flags]
public enum DaysOfWeek
{
None = 0,
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
Saturday = 64,
Weekend = Sunday | Saturday
} If you wanted to determine if a given (value.HasFlag(DaysOfWeek.Monday) || value.HasFlag(DaysOfWeek.Tuesday) || value.HasFlag(DaysOfWeek.Wednesday) || value.HasFlag(DaysOfWeek.Thursday) || value.HasFlag(DaysOfWeek.Friday)) or this without (value & DaysOfWeek.WeekDays) != 0 I don't think this example is too far-fetched and can definitely see this being used on something like |
IMHO, this is the best way to check for presence of any flag: There is no ambiguity about what it does, and it is more concise and faster than |
You could use that same argument for not adding Could we consider any other flag enum operations as proposed in dotnet/corefx#34079? The one I consider to have the most value in that proposal is the Also, if we ever want to make using System;
using static System.Enum;
namespace ConsoleApplication1
{
public static class Program
{
public static void Main()
{
// IsDefined can not be found if it's promoted to an extension method as using
// static requires extension methods to be invoked with extension method syntax
Console.WriteLine(IsDefined(DayOfWeek.Wednesday));
}
}
} I think |
To make progress, it may be best to scope this issue to just |
Fair enough. My other point about the extension method issue should be discussed however. |
C# doesn't allow extension methods inside of non-static classes. This would require a new type (like |
We should include var x = Enum.HasFlag(dayOfWeek, DaysOfWeek.Monday);
var y = (dayOfWeek & DaysOfWeek.Monday) == DaysOfWeek.Monday; However, I really don't think var x = Enum.HasAnyFlag(dayOfWeek, DaysOfWeek.Workdays);
var y = (dayOfWeek & DaysOfWeek.Workdays) != 0; |
@terrajobst The generic HasFlag does not provide internal consistency. The generic HasFlag is static method, but the non-generic method is a instance method that is more convenient to use.
You can write this today as more concise The syntax sugar provided by the generic API is longer. I do not see who would ever want to use this longer version in ordinary code. The only difference is going to be that the pattern matching in the JIT for this is going to need to be 50% more complex so that the generic API is also converted back to the efficient code.
Checking for all flags in the set is rare. The much more frequent flag operations are to check for exact one flag to be set or to check for any flag in a set to be set. As you have said the C# syntax for that is shorter. Everybody should better be using that in the first place to save on the busy work required to make the |
I see; namespace System
{
public partial abstract class Enum
{
public static TEnum[] GetValues<TEnum>() where TEnum : struct, Enum;
public static string GetName<TEnum>(TEnum value) where TEnum : struct, Enum;
public static string[] GetNames<TEnum>() where TEnum : struct, Enum;
public static bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum;
}
} |
I'd love to submit the PR for these additions but would like to wait until the repo's are merged before doing so if no one else objects. |
Also, with nullability now in C# 8, |
No objections; this is going to be a 5.0 feature anyway. |
@TylerBrinkley still interested? |
re-adding approved label |
I suggest to implement Generic version of GetValues method in the Enum class.
In present, if we going to get an typed array of some enum values we have to write following code:
SomeEnum[] values = (SomeEnum[])Enum.GetValues(typeof(SomeEnum));
It seems as unconvenient way.
In my opinion, the following Generics-based syntax should seems shorter, more convenient and safely (by reason of type casting necessity):
SomeEnum[] values = Enum.GetValues<SomeEnum>();
The possible way to implement this syntax is:
This proposal is inspired by the great Generic-based version of Enum.TryParse method laying besides with legacy non-Generic version:
If proposal to implement Generic version of GetValues will be accepted I guess following Generics-based implementations of GetName GetNames should be implemented for symmetry reason:
I introduce possible implementation of this proposal in dotnet/coreclr/pull#16557
This is a small point improvement suggestion.
It can be implemented separately of as a part of total Enum improvements discussing in dotnet/corefx/#15453
The text was updated successfully, but these errors were encountered: