Skip to content
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

Add EnumMember API #28198

Closed
TylerBrinkley opened this issue Dec 13, 2018 · 4 comments
Closed

Add EnumMember API #28198

TylerBrinkley opened this issue Dec 13, 2018 · 4 comments
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Milestone

Comments

@TylerBrinkley
Copy link
Contributor

TylerBrinkley commented Dec 13, 2018

Split off from #20008

Rationale and Usage

Currently, to retrieve both the names and values of an enum's members requires two separate calls and requires you to use a for loop which is quite clumsy. Additionally the pattern to associate extra data with an enum member using Attributes is not directly supported and instead requires users to manually retrieve the Attributes via reflection. This pattern is commonly used on enums using the DescriptionAttribute, EnumMemberAttribute, and DisplayAttribute. There should be added direct support for the retrieval of Attributes applied to enum members.

What used to be this to retrieve both the names and values of an enum's members

var values = (MyEnum[])Enum.GetValues(typeof(MyEnum));
var names = Enum.GetNames(typeof(MyEnum));
for (int i = 0; i < values.Length; ++i)
{
    MyEnum value = values[i];
    string name = names[i];
}

now becomes this

foreach (var member in Enum.GetMembers<MyEnum>())
{
    MyEnum value = member.Value;
    string name = member.Name;
}

And what used to be this to retrieve the DescriptionAttribute.Description of an enum member

MyEnum value = ???;
string description = ((DescriptionAttribute)Attribute.GetCustomAttribute(
    typeof(MyEnum).GetField(value.ToString()),
    typeof(DescriptionAttribute),
    false))?.Description;

now becomes this

MyEnum value = ???;
string description = value.GetMember()?.Attributes.Get<DescriptionAttribute>()?.Description;

Proposed API

 namespace System {
     public abstract class Enum : ValueType, IComparable, IConvertible, IFormattable {
         // New Generic API
+        public static EnumMember<TEnum> GetMember<TEnum>(this TEnum value) where TEnum : struct, Enum;
+        public static EnumMember<TEnum> GetMember<TEnum>(string name) where TEnum : struct, Enum;
+        public static EnumMember<TEnum> GetMember<TEnum>(string name, bool ignoreCase) where TEnum : struct, Enum;
+        public static EnumMember<TEnum> GetMember<TEnum>(ReadOnlySpan<char> name) where TEnum : struct, Enum;
+        public static EnumMember<TEnum> GetMember<TEnum>(ReadOnlySpan<char> name, bool ignoreCase) where TEnum : struct, Enum;
+        public static IReadOnlyList<EnumMember<TEnum>> GetMembers<TEnum>() where TEnum : struct, Enum;

         // New Non-Generic API
+        public static EnumMember GetMember(Type enumType, object value)
+        public static EnumMember GetMember(Type enumType, string name);
+        public static EnumMember GetMember(Type enumType, string name, bool ignoreCase);
+        public static EnumMember GetMember(Type enumType, ReadOnlySpan<char> name);
+        public static EnumMember GetMember(Type enumType, ReadOnlySpan<char> name, bool ignoreCase);
+        public static IReadOnlyList<EnumMember> GetMembers(Type enumType);
     }
+    public abstract class EnumMember : IEquatable<EnumMember>, IComparable<EnumMember>, IComparable, IConvertible, IFormattable {
+        public ComponentModel.AttributeCollection Attributes { get; }
+        public string Name { get; }
+        public object Value { get; }
+        public bool Equals(EnumMember other);
+        public sealed override bool Equals(object other);
+        public sealed override int GetHashCode();
+        public sealed override string ToString();
+        public string ToString(string format);
+    }
+    public abstract class EnumMember<TEnum> : EnumMember, IEquatable<EnumMember<TEnum>>, IComparable<EnumMember<TEnum>> {
+        public new TEnum Value { get; }
+        public bool Equals(EnumMember<TEnum> other);
+    }
 }
 namespace System.ComponentModel {
-    public class AttributeCollection : ICollection, IEnumerable
+    public class AttributeCollection : ICollection, IEnumerable, IList<Attribute>, IReadOnlyList<Attribute> {
+        public TAttribute Get<TAttribute>() where TAttribute : Attribute;
+        public Attribute Get(Type attributeType);
+        public IEnumerable<TAttribute> GetAll<TAttribute>() where TAttribute : Attribute;
+        public IEnumerable<Attribute> GetAll(Type attributeType);
+        public bool Has<TAttribute>() where TAttribute : Attribute;
+        public bool Has(Type attributeType);
     }
 }

API Details

This proposal makes use of a C# language feature that needs to be added in order for this proposal to make the most impact.

This proposal specifies extension methods within System.Enum and as such requires C# to allow extension methods within non-static classes as is proposed in csharplang#301. Promoting these to extension methods later would be a breaking change due to csharplang#665 but I feel this is acceptable.

Alternatively, the extension methods could be defined in a separate static EnumExtensions class. This is uglier but would avoid this issue and the extension methods would be available immediately instead of needing to wait for a later C# version to support this.

This proposal stems from my work on the open source library Enums.NET.

Enum API Details

  • GetMember retrieves the EnumMember with the specified value or name. If there are no enum members with the specified value or name null is returned.
  • GetMembers retrieves all of the EnumMembers of the specified enum in increasing significance bit order by their respective values.

EnumMember API Details

  • EnumMember and EnumMember<TEnum> are a new object model formed over an enum member which include its name, value, and attributes. They only have internal constructors and there is only one instance of EnumMember created for each enum member thus one can use ReferenceEquals for determining equality. This also prevents allocations after the first retrieval.
  • It seems there's no reason to constrain TEnum in EnumMember<TEnum> to an Enum as it's not publicly constructible and being unconstrained would be beneficial in unconstrained generic programming.

AttributeCollection API Details

  • Get returns the first Attribute in the collection that is assignable to the specified attribute type if available otherwise null.
  • GetAll returns all Attributes in the collection that are assignable to the specified attribute type.
  • Has indicates if any Attributes in the collection are assignable to the specified attribute type.
  • Type needs to be moved from the System.ComponentModel.TypeConverter assembly into mscorlib while adding an automatic binding redirect.

Implementation Details

A type forward would need to be added for AttributeCollection so that it's available from corelib.
Utilizes performance improved implementation from #20008.

Updates

  • Changed GetMembers to return an IReadOnlyList instead of IEnumerable.
@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the 5.0 milestone Feb 1, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@joperezr joperezr added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed untriaged New issue has not been triaged by the area owner labels Jul 6, 2020
@joperezr joperezr modified the milestones: 5.0.0, Future Jul 6, 2020
@terrajobst terrajobst removed the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jul 11, 2020
@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jul 21, 2020
@terrajobst
Copy link
Member

terrajobst commented Jul 21, 2020

Video

We should add this API:

namespace System
{
    public partial class Enum
    {
        public static T[] GetValues<T>();
    }
}

So this code:

var values = (MyEnum[])Enum.GetValues(typeof(MyEnum));
var names = Enum.GetNames(typeof(MyEnum));
for (int i = 0; i < values.Length; ++i)
{
    MyEnum value = values[i];
    string name = names[i];
}

becomes

var values = Enum.GetValues<MyEnum>();
foreach (var value in values)
{
    var name = value.ToString();
}

With respect to custom attributes, you can already do this:

FieldInfo enumField = ...;
var description = enumField.GetCustomAttributes<DescriptionAttribute>()
                           .SingleOrDefault()?.Description ?? "";

@jkotas
Copy link
Member

jkotas commented Jul 21, 2020

Generic Enum.GetValues was approved as #2364 and added by #33589 two weeks ago.

The EnumMember family of APIs was rejected as too high-level for System.Enum, so there is nothing left to do. @terrajobst Is this the right conclusion?

@TylerBrinkley
Copy link
Contributor Author

Thanks for the consideration. Enum.GetValues<TEnum>() was already added in #2364 so no work to be done there. While .ToString() doesn't handle the duplicate values case I understand if this API is too high-level here, especially including the reflection case of attributes.

@terrajobst
Copy link
Member

Generic Enum.GetValues was approved as #2364 and added by #33589 two weeks ago.

Ha, my spider senses told me we already had the API :-)

The EnumMember family of APIs was rejected as too high-level for System.Enum, so there is nothing left to do. @terrajobst Is this the right conclusion?

Correct

@ghost ghost locked as resolved and limited conversation to collaborators Dec 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Projects
None yet
Development

No branches or pull requests

6 participants