-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Proposal: Nested enum
with implicit conversion
#587
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 |
And I'm not married to allowing |
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! |
This would make certain enum interop definitions much more clear as well. |
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 ;( |
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. |
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. :-) |
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). |
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... |
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.) |
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 |
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. |
Why? How is this any different from:
|
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. |
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
} |
@sigru partial enum MessageType {
None // this has value '0'
}
partial enum MessageType SubMessageType {
Message1 = 1,
Message2, Message3
} This explicitly makes 'SubMessageType' |
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. |
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. |
Well, it wouldn't work in my cases, as ExtendedEventTypes is effectively completely separate from EventType, and the method overrides wouldn't work. |
@bondsbw |
@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. |
@lachbaer I'm not against But I don't see how |
@bondsbw |
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. |
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 |
So long as those Though when we start talking about whether |
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. |
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. |
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
|
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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.
The text was updated successfully, but these errors were encountered: