Proposal: Nested enum
with implicit conversion
#8727
Replies: 29 comments
-
I'd also like a mechanism to include external enums. Something like // Provided by library
public enum PrimaryColor { Red, Yellow, Blue } // My code
public enum MyColor
{
enum PrimaryColor,
enum SecondaryColor
{
Orange,
Green,
Purple
},
Amber,
Teal,
Magenta
} Then I can write // Equivalent
MyColor r1 = PrimaryColor.Red;
MyColor r2 = MyColor.Red;
// Equivalent
MyColor o1 = SecondaryColor.Orange;
MyColor o2 = MyColor.Orange;
// As always
MyColor m = MyColor.Magenta; But these would not be allowed: PrimaryColor r3 = MyColor.Red; // ERROR
PrimaryColor o3 = MyColor.Orange; // ERROR |
Beta Was this translation helpful? Give feedback.
-
And I'm not married to allowing |
Beta Was this translation helpful? Give feedback.
-
Of course the conversion rules should be exactly as with I like the idea of including other enums by name as well. However then the possibilites of duplicate values is high. The programmer must take care of that! |
Beta Was this translation helpful? Give feedback.
-
This would make certain enum interop definitions much more clear as well. |
Beta Was this translation helpful? Give feedback.
-
I did the same request last year when the discussion was in roslyn repository: Two months later, @sirgru did to: And people are writing ugly code to do something similar: But it looks like the C# Language Design Team didn't like it ;( |
Beta Was this translation helpful? Give feedback.
-
There simply isn't a champion for it. That may also mean that no LDM members think this is important enough compared to the current set of work we've picked out. |
Beta Was this translation helpful? Give feedback.
-
Can we let this quote stand out for its own? 😁 Anyway, this place is a pool of ideas and as long as a topic is still open, it might gafter - ehm, gather - interest for a much later X.X release somewhere in the future. @CyrusNajmabadi nevertheless it would be nice to have a very little feedback from the LDM team whether this feature is interesting per se or whether it has fallen fallen into disgrace from the very start. :-) |
Beta Was this translation helpful? Give feedback.
-
As mentioned I wanted this functionality a while ago in dotnet/roslyn#14720.
From what I've seen people are against suggestions of this nature as they don't see the benefit in having them (judged by the amount of downvotes on all similar proposals). |
Beta Was this translation helpful? Give feedback.
-
I would like to hear the reasons why people doesn't like it, if it is because it isn't important enough or if they think that this would lead people to write worst code... |
Beta Was this translation helpful? Give feedback.
-
Passing in an EDIT: Actually after reading over your comment again, I may be misinterpreting it. If you cannot pass the new values into the existing API, then it shouldn't pose this problem. (I wouldn't call that "inheritance", since inheritance implies substitutability.) |
Beta Was this translation helpful? Give feedback.
-
Well, I don't really want the new values to be able to go into the new API as it can't handle them obviously. Here are a couple of examples of what I would want off the top of my head: // Library
public enum EventType {
Event0, Event1
}
public abstract class MyBase {
public virtual void SomeGeneralEventProcessor (BlaBla argument, EventType eventType) {
// Does something different depending on the event type
}
public abstract void MainDecision(List<EventType> aggregatedEvents);
}
// Project
public enum ExtendedEventTypes : EventType { // We have new, project-specific event types in addition to pre-defined events.
Event2, Event3, Event4 // The allowed range of values is Event0, Event1, Event2, Event3, Event4
}
public class MyEventWorkingClass : MyBase {
// Be able to override the abstract method
public override void MainDecision(List<ExtendedEventTypes> aggregatedEvents) { // Still be able to do the override with the extended enum; either that or allow substitutability and have the possibility of unhandled behavior when supplied in the library.
// Do the actual work
foreach(var event in aggregatedEvents) {
if(event is EventType ) { // meaning it's either Event0 or Event1
base.SomeGeneralEventProcessor (BlaBla argument, (EventType) event);
} else {
LocalEventProcessor (BlaBla argument, event);
}
}
}
} So, it's the ability to have new enum values and be able to extend behaviors depending on them. // Library
public enum MessageTypeBase {
None
}
public interface IMessageable {
void ReceiveMessage(Message message);
}
public class Message {
public IMessageable from;
public IMessageable to;
public MessageTypeBase type;
public float delay;
public System.Object[] data;
public Message(IMessageable from, IMessageable to, MessageTypeBase type, float delay = 0, params System.Object[] data) {
this.from = from;
this.to = to;
this.type = type;
this.delay = delay;
this.data = data;
}
}
// Project
public enum MessageType : MessageTypeBase {
Message1, Message2, Message3
}
// ...
MessageQueue.AddMessage(new Message(this, other, MessageType.Message2)); // requires treating the MessageType enum polymorphically. Edit: I guess in order to avoid the the enum inheritance problem, we could have the |
Beta Was this translation helpful? Give feedback.
-
It's very dangerous to have values from 'MessageType', that are not part of 'MessageTypeBase', to be assigned to variables of 'MessageTypeBase'! This wouldn't work this way. In this case it would be probably better to create an "enum class" that has full power over inheritance. class EventType
{
public const int Event0 = 0;
public const int Event1 = 1;
}
class ExtendedEventTypes : EventType
{
public const int Event2 = 2;
public const int Event3 = 3;
} It would be worth a proposal to shorten this by typing
With what value does 'Event2' start? In this case it seems to be easy, but what if you had this construction: enum EventType { Event1 = 1 }
enum AdvancedEventType { Event2 = 15, Event3 }
enum ExtremeEventType { Event4 = 3 }
enum SuperduperEventType : EventType, AdvancedEventType, ExtremeEventType { Event5 } What value will Event5 have? It all gets a bit messy with this. Therefore I stick with the initial idea. |
Beta Was this translation helpful? Give feedback.
-
Why? How is this any different from:
|
Beta Was this translation helpful? Give feedback.
-
1st: the cast, 2nd: you must explicitly cast and thus think about what you're doing. The other example implicitly casts and assigns invalid values without explicitly noticing. |
Beta Was this translation helpful? Give feedback.
-
I don't think the enum class would contribute to safety over something like partial enum. The unsafe part would be an unexpected value, which would remain unhandled by the original library (or manually throw an exception). Something like // Lib
partial enum MessageType {
None
}
// local
partial enum MessageType {
Message1, Message2, Message3
} |
Beta Was this translation helpful? Give feedback.
-
@sigru partial enum MessageType {
None // this has value '0'
}
partial enum MessageType SubMessageType {
Message1 = 1,
Message2, Message3
} This explicitly makes 'SubMessageType' |
Beta Was this translation helpful? Give feedback.
-
This is why I originally went the inheritance way, it is significant which enum is first and which comes after that, otherwise there can be problems with serialization among toehr things. Also, that's why I had the The first value being mandatory can actually be problematic. If the original adds another enum value, then that can mean a breaking change for clients. Worse, if clients have serialized values of the enum of the previous int saved to disk, and they change the initial int value to now compile with the new library, the serialized values now mean something else. |
Beta Was this translation helpful? Give feedback.
-
Is there something problematic with the extension syntax I described above, which is modeled on the syntax of this proposal? i.e. // Library
public enum EventType
{
Event0,
Event1
}
// Project
public enum ExtendedEventTypes
{
enum EventType,
Event2,
Event3,
Event4
} The issue of inheritance has been discussed before, which is why I purposely avoided inheritance syntax and all the issues that come as a result. |
Beta Was this translation helpful? Give feedback.
-
Well, it wouldn't work in my cases, as ExtendedEventTypes is effectively completely separate from EventType, and the method overrides wouldn't work. |
Beta Was this translation helpful? Give feedback.
-
@bondsbw |
Beta Was this translation helpful? Give feedback.
-
@sirgru In your first example, you are overriding a method by changing its signature (in your case it changes the argument type Hiding the method or using a different method name would not break inheritance. If you need the new method to call the base method, then you will need to work out how to properly convert any extended enum values into the original enum type. EDIT: Actually I'm not sure this really breaks things. The new enum isn't "inheriting" the original (a subset type). It is a superset type. It would be akin to the following: public class A
{
public virtual void Foo(string s) { }
}
public class B : A
{
public override void Foo(object o) { } // object is a superset type of string
} It isn't currently legal to make a contravariant change to a parameter type in a derived class, but I'm not sure it would actually violate type safety. It might create more confusion than it's worth, however. |
Beta Was this translation helpful? Give feedback.
-
@lachbaer I'm not against But I don't see how |
Beta Was this translation helpful? Give feedback.
-
@bondsbw |
Beta Was this translation helpful? Give feedback.
-
Ok, in that case it is just confusing. If the initial proposal is implemented, then to me it makes more sense to build on that syntax for extension than to come up with something completely different that puts an alternative definition on existing keywords. |
Beta Was this translation helpful? Give feedback.
-
It can both be combined to take the best of both worlds. // file1.cs
enum SubMessageType {
Message2 = 2,
Message3
}
// file2.cs
partial enum MessageType {
None, // this has value '0'
Message1
}
// file3.cs
partial enum MessageType {
let enum SubMessageType // `let` = include existing enum instead of creating a new nested one
}
// Enum.GetValues(typeof( SubMessageType )) = 2, 3
// Enum.GetValues(typeof( MessageType )) = 0, 1, 2, 3 |
Beta Was this translation helpful? Give feedback.
-
So long as those Though when we start talking about whether |
Beta Was this translation helpful? Give feedback.
-
This discussion was forked into "nested enum" and "enum inheritance" and already take the way of enum inheritance. Maybe it's better to change the title and description of this issue to lead to enum inheritance only, and create a separate proposal for nested enum. |
Beta Was this translation helpful? Give feedback.
-
A concern raised at #1569 needs to be addressed by this proposal. Namely, how do the default values get assigned for the wrapper/superset enums, and do they overlap. |
Beta Was this translation helpful? Give feedback.
-
So it would have to be something like public enum MyColor
{ //
enum PrimaryColor, // [0]
// {
// Red, [0][0]
// Yellow, [0][1]
// Blue [0][2]
// }
enum SecondaryColor // [1]
{
Orange, // [1][0]
Green, // [1][1]
Purple // [1][2]
},
Amber, // [2]
Teal, // [3]
Magenta // [4]
} If underlying type of
Limits:
Without indexing groups, continue numbering?So in this case: // Provided by library
public enum PrimaryColor { Red, Yellow, Blue } public enum MyColor
{
enum PrimaryColor,
enum SecondaryColor
{
Orange,
Green,
Purple
},
Amber,
Teal,
Magenta
}
Not
|
Beta Was this translation helpful? Give feedback.
-
When there is an
enum
that has several subsections that could be enums of their own, it would be nice to have those subsections being nested enums.Now:
This makes the need for helper functions, that must not be forget to be adapted when a section is extended.
Instead a nesting would be fine, that produces duplicate sub-enums with the same same visibility, underlying type and attributes as the enclosing enum.
GetContextualKeywordKinds
can now be replaced with the standardEnum.GetValues(typeof(SyntaxKindPunctuation))
instead of a custom function.Within the same assembly the nesting inheritance is resolved by the compiler, without further casting.
With CLR support this implicit casting can be extend to external assemblies as well.
Beta Was this translation helpful? Give feedback.
All reactions