csharplang
csharplang copied to clipboard
Champion "permit methods in enum declarations"
- [ ] Proposal added
- [ ] Discussed in LDM
- [ ] Decision in LDM
- [ ] Finalized (done, rejected, inactive)
- [ ] Spec'ed
See also https://github.com/dotnet/roslyn/issues/3704
Reminder (since we don't yet have a work items list for this):
- [ ] Let's review all the existing attributes that are allowed on
class
orstruct
contexts, but notenum
. Maybe they should be updated to allowenum
too. For example, SkipLocalsInitAttribute.
This would allow me to get rid of so much {EnumName}Extensions
clutter.
One thing that this would probably enable is overriding ToString
for enums.
My understanding is that this is not allowed by the CLR spec (I.8.5.2). Are there plans to change that?
The following restrictions are listed:
- It shall have exactly one instance field, and the type of that field defines the underlying type of the enumeration.
- It shall not have any methods of its own.
- It shall derive from System.Enum (see Partition IV Library – Kernel Profile).
- It shall not implement any interfaces of its own.
- It shall not have any properties or events of its own.
- It shall not have any static fields unless they are literal.
I imagine that removing all the restictions except for the single field and System.Enum requirements should be possible. (Although having events would be kind of useless because of the single field restriction.)
@MI3Guy That is a good point. We'll consider this when next we have the opportunity to make CLR changes.
I prefer writing extension method
But well, I think what the proposer have explain. We don't need CLR changed. He just propose that we can just transpile enum method into extension method
This feature won't truly shine until C# also supports enum inheritance and enum generic constraints. I run into enum limitations all the time in interop.
I think this is probably a feature best left for ADTs.
Sure, methods could be added to enums but once C# has types such as discriminated unions (which can have methods) I think the use of raw enums will decrease dramatically.
This is something that is very much needed. I don't like having to keep it all as extension methods; it's counter intuitive and requires more typing,
Maybe not entirely related, but probably worth discussing in this context: Is it possible to start emitting enum
s as a readonly
type?
Admittedly, that may be a bit much. Currently, the value__
field isn't initonly
, and I don't know if making that change would break assumptions elsewhere. But I think they should definitely get as close to readonly
behavior as possible. Wouldn't it then be best to implicitly treat all enum-methods as a (yet only proposed) "readonly" method?
I believe the compiler already treats enums as readonly.
If we allowed overriding ToString
as an exception to the "no methods" rule, and just transpiled other properties/methods into extensions (at this point, probably using the new #5497 extension feature), it should be simple to accomplish. Right?
Relaxing the CLR spec (or generally making changes to the CLR in tandem with C# releases) is something that has been happening. The appetite to do this is back now that the only people affected by CLR changes (in .NET Core/5+) are people who overrode defaults and asked to be affected.
@jnm2 hopefully allowing to override ToString
only is easy enough. The only reason I can think of to make other methods/properties as true "instance" members would be to allow virtual members, but I'm not sure that makes sense for enums anyway.
Regarding syntax, we need to decide where enum options stop and other members start. I propose semicolon:
enum Color
{
Red,
Green,
Blue;
public override string ToString()=>"asd";
public void Foo() { ... }
public int Prop => ((int)this) + 1;
// ...
}
@TahirAhmadov Another reason not to emit extension methods is that the C# compiler would be generating a public class name.
Another reason not to emit extension methods is that the C# compiler would be generating a public class name.
I actually think it's a benefit.
For example, if #5497 is released first with C# version M, and enum
methods later with C# version N, then creating extension
s allows those who are on C# M to consume libraries created on C# N and simply use the functionality - which to them looks like the same extension
which is already supported in C# M.
Similarly, assuming extension
can be implemented similarly to current "extension methods" (meaning, emitted as special static classes with special attributes), then somebody on C# 10 can consume libraries created using C# N, and they will just see static classes with the required functionality. Yes, using it is a bit awkward - they would have to do something like ColorExtensions123.Foo(Color.Red)
, but that's a minor stylistic difference only; they can still get their jobs done.
I know, it's tempting to say in a couple of years, "hey why are you still on C# 10", but realistically, in many companies management is wary of frequent upgrades and view them as risk. I disagree with that mantra personally; but in the real world, it's prevalent enough that I think we shouldn't shut those teams out completely.
Another thought I had: together with methods, enums should allow declaring static fields; then scenarios like below become possible:
enum Foo
{
A,
B;
static readonly string[] _displayNames = new[] { "Alpha", "Beta" };
public string DisplayName => _displayNames[(int)this];
}
I found an Oracle patent for object-oriented enums mentioned in other discussions. Does that mean this is legally blocked until the patent's expiration date (2024-02-01
)?
@ds5678
That patent covers an implementation of enums as instances of a reference type with a somewhat specific shape. IANAL, I don't think allowing arbitrary methods within an enum declaration would fall afoul of that patent
I don't think allowing arbitrary methods within an enum declaration would fall afoul of that patent
I hope you're right. I'd love to get rid of my numerous {EnumName}Extensions
classes. They've always felt like a workaround.
~If this is implemented, will it be a runtime-dependent feature, i.e. only available on .NET 8+?~ Yes
Just dropping a thought about this feature here, would it be nicer to force separating the other members of the enum into a different declaration? That would also require support for partial enums.
In each partial enum declaration, you can only have either normal members, or the enum's fields. That means, defining such an enum would look like this:
public partial enum S
{
None,
A,
B,
C,
}
partial enum S
{
public bool IsValid => this is A or B;
// would be nice to automatically access the max value as a compile-time constant
// without having to define it manually everywhere
// also for min value and total count
private const int MaxValue = (int)C + 1;
// either directly use value__ or a friendlier identifier to access the internal value
public S Increment() => (S)((this.value__ + 1) % this.MaxValue);
}
Maybe along with a rule that only one enum part can contain implicitly numbered members.
That touches the proposal about partial enums and the declaration of their fields, but definitely some rules must be enforced to ensure that field declaration doesn't go out of hand.
One thing that this would probably enable is overriding
ToString
for enums.
This would eliminate the common mistake where the developer forgets to call an extension method during string interpolation, causing the enum's ToString
method to get called under the hood.
enum EmployeeType { Manager, Staff }
static class EmployeeTypeExtensions
{
static string GetTitle(this EmployeeType employeeType) => employeeType switch
{
EmployeeType.Manager => "Team Leader",
EmployeeType.Staff => "Team Member"
}
}
static string GetEmployeeTitle(Employee employee)
{
string titleVerbiage = $"Employee title: {employee.EmployeeType}"; // oops, should've been employee.EmployeeType.GetTitle()
}
Have to be careful though. Override ToString
and JSON deserialization might break.