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

String-based enums #2849

Open
terrajobst opened this issue Oct 2, 2019 · 161 comments
Open

String-based enums #2849

terrajobst opened this issue Oct 2, 2019 · 161 comments

Comments

@terrajobst
Copy link
Member

terrajobst commented Oct 2, 2019

We've noticed a trend, especially in cloud services, that there is a need for extensible enums. While enums can in principle be extended by casting any int to the enum, it has the risk for conflicts. Using strings has a much lower risk of conflicts.

In the BCL, we've called this concept "strongly typed strings". Examples are:

It would be nice if we could make this a language feature so that instead of this:

public readonly struct OSPlatform : IEquatable<OSPlatform>
{
    private string _value;

    public static OSPlatform FreeBSD { get; } = new OSPlatform("Free BSD"); 
    public static OSPlatform Linux { get; } = new OSPlatform(nameof(Linux)); 
    public static OSPlatform Windows { get; } = new OSPlatform(nameof(Windows));

    public OSPlatform(string value)
    {
        _value = value;
    }

    public override bool Equals(object obj)
    {
        return obj is OSPlatform p && Equals(p);
    }

    public bool Equals(OSPlatform other)
    {
        return _value == other._value;
    }

    public static bool operator ==(OSPlatform left, OSPlatform right) => Equals(left, right);

    public static bool operator !=(OSPlatform left, OSPlatform right) => !Equals(left, right);

    public static explicit operator string(OSPlatform value) => value.ToString();

    public static explicit operator OSPlatform(string value) => new OSPlatform(value);

    public override string? ToString() => _value;
}

one only has to type this:

public enum OSPlatform : string
{
    FreeBSD = "Free BSD",
    Linux,
    Windows
}

/cc @pakrym @heaths @JoshLove-msft

Discriminated Unions

As was pointed out by @DavidArno, this won't be solved by discriminated unions because those are about completeness. The primary value of string-based enums is that they are extensible without changing the type definition:

OSPlatform platform = (OSPlatform)"Apple Toaster with Siri Support";

This is vital for things like cloud services where the server and the client can be on different versions.

@pakrym
Copy link

pakrym commented Oct 2, 2019

HttpMethod is another example. It also has to be possible to set values different from identifier names

public enum OSPlatform : string
{
    FreeBSD = "free bsd",
    Linux = "linux",
    Windows = "windows" 
}

@HaloFour
Copy link
Contributor

HaloFour commented Oct 2, 2019

Seems like something that could be hammered out alongside DUs, which also feel like enums.

IMO, the syntax in this case should also allow for the named member to reference a different string in the case that the string either doesn't fit with the naming conventions of C# members or doesn't fit the rules for being an identifier.

@abock
Copy link

abock commented Oct 2, 2019

I've always wanted this, and would further want the equality check/deserialization to be case-insensitive, or at least configurable for that. For example, "Linux" and "linux" could both deserialize to OSPlatform.Linux.

@benaadams
Copy link
Member

HttpMethod as you say and HeaderNames also. HeaderNames having non-C# values in the actual string values such as - in Content-Length; so being able to specify the string much like the actual numeric value can be specified on current enums would be good.

@heaths
Copy link

heaths commented Oct 2, 2019

In Azure SDK for .NET, so far we've settled on a structure defined like in https://gist.github.com/heaths/d105148428fe09a2631322b656f04ebb. The main problem comes from a lack of IntelliSense built into VS or VSCode/OmniSharp. If there were a way to enabled this - perhaps through Roslyn - such that MyStruct x = would pop visible static readonly fields or properties, that would satisfy much of the concern around discoverability.

@pakrym
Copy link

pakrym commented Oct 2, 2019

Another question is if [Flags] should be somehow supported for these.

And switch statement/expression support.

@abock
Copy link

abock commented Oct 2, 2019

Yes for [Flags] support! Not entirely sure how to represent it from an API perspective, but it seems natural to have string enums implement IEnumerable<string> to yield all of the set "bits".

Also, and without giving it much thought, what if tuples could be used to specify multiple acceptable variations of the string value:

[Flags]
public enum OSPlatform : string
{
    Linux = ("Linux", "linux", "LINUX")
}

@abock
Copy link

abock commented Oct 2, 2019

I'd also like to see something that interops well with Xamarin.iOS/Mac. String enums are a large part of the native API surface of macOS and iOS. Swift's enums were designed with this in mind as well.

@terrajobst
Copy link
Member Author

I'm not sold on flags support. This would either require string parsing with some separator or storing them as a collection, which seems unnecessarily heavyweight for the general case.

@terrajobst
Copy link
Member Author

@abock

I'd also like to see something that interops well with Xamarin.iOS/Mac. String enums are a large part of the native API surface of macOS and iOS. Swift's enums were designed with this in mind as well.

Could you describe what this would entail?

@bchavez
Copy link

bchavez commented Oct 2, 2019

One thing I love doing with enum-ish strings in C# is to emulate Ruby symbols like this:

namespace StringSymbols
{
   public static class OSPlatform
   {
      public const string FreeBSD = nameof(FreeBSD);
      public const string Linux = nameof(Linux);
      public const string Windows = nameof(Windows);
   }
}

Then use it like this:

using System;
using static StringSymbols.OSPlatform;

namespace StringSymbols
{
   class Program
   {
      static void Main(string[] args)
      {
          var os = Windows;
          
          if( os == Linux ){
              Console.WriteLine("Hello Linux, command line ninja");
          }
          else if( os == Windows ){
              Console.WriteLine("Hello Windows, seattle sunshine");
          }
          else if( os == FreeBSD ){
              Console.WriteLine("Hello FreeBSD, go cal bears, go");
          }
      }
   }
}

Output:

Hello Windows, seattle sunshine

🗻 🤽‍♂️ Creedence Clearwater Revival - Green River

@normj
Copy link

normj commented Oct 2, 2019

Here is an example of what we do in the AWS .NET SDK to solve this problem. Our main requirement is to be forward compatible with enum values that a service might return in the future.

    /// <summary>
    /// Constants used for properties of type ContainerCondition.
    /// </summary>
    public class ContainerCondition : ConstantClass
    {

        /// <summary>
        /// Constant COMPLETE for ContainerCondition
        /// </summary>
        public static readonly ContainerCondition COMPLETE = new ContainerCondition("COMPLETE");
        /// <summary>
        /// Constant HEALTHY for ContainerCondition
        /// </summary>
        public static readonly ContainerCondition HEALTHY = new ContainerCondition("HEALTHY");
        /// <summary>
        /// Constant START for ContainerCondition
        /// </summary>
        public static readonly ContainerCondition START = new ContainerCondition("START");
        /// <summary>
        /// Constant SUCCESS for ContainerCondition
        /// </summary>
        public static readonly ContainerCondition SUCCESS = new ContainerCondition("SUCCESS");

        /// <summary>
        /// This constant constructor does not need to be called if the constant
        /// you are attempting to use is already defined as a static instance of 
        /// this class.
        /// This constructor should be used to construct constants that are not
        /// defined as statics, for instance if attempting to use a feature that is
        /// newer than the current version of the SDK.
        /// </summary>
        public ContainerCondition(string value)
            : base(value)
        {
        }

        /// <summary>
        /// Finds the constant for the unique value.
        /// </summary>
        /// <param name="value">The unique value for the constant</param>
        /// <returns>The constant for the unique value</returns>
        public static ContainerCondition FindValue(string value)
        {
            return FindValue<ContainerCondition>(value);
        }

        /// <summary>
        /// Utility method to convert strings to the constant class.
        /// </summary>
        /// <param name="value">The string value to convert to the constant class.</param>
        /// <returns></returns>
        public static implicit operator ContainerCondition(string value)
        {
            return FindValue(value);
        }
    }

@aensidhe
Copy link

aensidhe commented Oct 2, 2019

I believe DU should be sufficient for this.

@Liminiens
Copy link

This proposal is a a special case of Discriminated Unions.

@kkokosa
Copy link

kkokosa commented Oct 2, 2019

Maybe a more general concept of typed strings, taken from Bosque language?

var code: String<Zipcode> = Zipcode'02-110';
entity PlayerMark provides Parsable {
	field mark: String;

	override static tryParse(str: String): PlayerMark | None {
		return (str == "x" || str == "o") ? PlayerMark{ mark=str } : none;
	}
}

So in example scenario from above it would be:

String<OSPlatform> platform = OSPlatform"Linux"

@DavidArno
Copy link

@HaloFour, @aensidhe & @Liminiens,

I don't agree that this proposal is equivalent to DUs. The latter are designed to be closed sets of values, that cannot be extended. Whereas this proposal is specifically requesting that it be a set of values that are open to extension as other enums are.

@aensidhe
Copy link

aensidhe commented Oct 2, 2019

@DavidArno what do you mean by cannot be extended? To extend enum (both int-based and string-based) we need to write code. Same with DU. I don't get the difference.

@dsaf
Copy link

dsaf commented Oct 2, 2019

String enums in three years are better than discriminated unions in six years.

@DavidArno
Copy link

@dsaf, DUs in one year (C# 9) would be way better than either of your options 😄

@aensidhe, if I declare a DU:

type IntOrBool = 
   | I of int 
   | B of bool

I can't then add an S of string to it without changing the type definition. With this proposal though, for the enum:

public enum OSPlatform : string
{
    FreeBSD = "Free BSD",
    Linux,
    Windows
}

I could write var os = (OSPlatform)"Banana"; as legitimate code. DUs can't be extended; enums can.
(At least this is my understanding of "...there is a need for extensible enums. While enums can in principle be extended by casting any int to the enum, it has the risk for conflicts. Using strings has a much lower risk of conflicts.".)

@alrz
Copy link
Member

alrz commented Oct 2, 2019

and HeaderNames also.

I think the important distinction is that HeaderNames should not be a "type" anyways e.g. header names are not restricted to those values, so I think it's better off to be defined as a set of predefined constants.

@d3v3us
Copy link

d3v3us commented Oct 2, 2019

Great idea, I always wanted enum, which is quite neutral type, could inherit from string type.

@DavidArno
Copy link

DavidArno commented Oct 2, 2019

As I've downvoted this proposal, I feel I should explain why, even though the majority may disagree with me and downvote this...

To my mind, the statement "...there is a need for extensible enums..." is fundamentally flawed. The fact that enums are extensible causes bugs in code and causes me to have to check that the actual value matches one of the defined values. It's on par with the null's "billion dollar mistake". And it makes using enums with switch expressions clunky:

enum Values { ValueA, ValueB }

class C 
{
    public int Foo(Values value)
        => value switch {
            Values.ValueA => 0,
            Values.ValueB => 1
        };
}

gives me the warning, warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive).. So I need to add a default case to suppress that warning. But if I then add ValueC to the enum, I get no warning/error that I'm not explicitly handling it.

To my mind, allowing enums to be any int value, rather than just the defined values, was a design mistake. Extending that mistake to strings too would be a bad thing to do.

@popcatalin81
Copy link

@DavidArno Your objection has nothing to do with string enums per se, but how enums were implemented in C#. Totally different issues.

@tabakerov
Copy link

@DavidArno 's example with casting any string to the string enum is a good reason to downvote this proposal.

  public enum OSPlatform : string
  {
      FreeBSD = "Free BSD",
      Linux,
      Windows
  }
  ...
  var os = (OSPlatform)"Banana";
  ...
  💥

DU, one more time, is the right and proper way to handle "strongly typed string in BCL" situation. And language should not be changed to fix it by allowing string enums (which are not strongly typed at all), but BCL (and others) should adopt DU approach.
Otherwise it's kinda like adding one more floor to a sand castle

@benaadams
Copy link
Member

and HeaderNames also.

I think the important distinction is that HeaderNames should not be a "type" anyways e.g. header names are not restricted to those values, so

Nor are HttpMethods, platforms etc; the point is enums are an open definition.

example with casting any string to the string enum is a good reason to downvote this proposal.

That is why they are enums and not DUs, much like this is valid for enums currently

public enum OSPlatform : int
{
      Linux = 1,
      Windows = 2
}
// ...
var os = (OSPlatform)3;

Which is the same point with headers or methods

[CaseInsensitive]
public enum HeaderNames : string
{
    Accept = "Accept",
    AcceptCharset = "Accept-Charset",
    AcceptEncoding = "Accept-Encoding",
    AcceptLanguage = "Accept-Language",
    AcceptRanges = "Accept-Ranges",
    // ...
}
// ...
var requestIdName = (HeaderNames)"X-Request-Id";

if ((HeaderNames)"x-request-id" == requestIdName) 
{
    // is true
}

@Liminiens
Copy link

@benaadams this proposal shows the demand for enums being more complex than just a set of flags or something. When you really have to do want that - you should use DU’s if they exist in a language, which provide compile time checks for exhaustiveness and extensibility via adding methods to type and new cases.

With enums the only way you can provide safety while casting is using TryParse - style methods.

And there is a question: what would guidelines be when eventually DU’s are added to the language? Would string enums become obsolete because casting values to them isn’t “safe”?

@HaloFour
Copy link
Contributor

HaloFour commented Oct 2, 2019

If these "String enums" can just be any arbitrary values of string then I'm opposed to them too. I think that a named type should bring with it some degree of type safety. While current enums can be any integral value it's a pretty exceptional case for the value to not be one of the declared members, outside of flags enums.

Taking a page from pretty much HTTP package in Java, you often do have common things like HTTP headers and methods expressed as enums, but any method that accepts such an enum also has an overload that accepts a String.

This sounds like a much better opportunity for proper DUs, where you can have an Other(string value) case.

@TylerBrinkley
Copy link

I've personally had need for something like this when creating a web API client library where a returned objects options are specified as a set of string values. A fixed set of string values mapped from an enum in the client library was inadvisable because if or when more options are added the client library would break upon deserialization.

To address this need for strongly typed string values I created a StringEnumValue<TEnum> type which is implicitly convertible to and from both a string value and a TEnum? value.

A string based enum would have been a much better option if it had been available at the time.

@benaadams
Copy link
Member

If these "String enums" can just be any arbitrary values of string then I'm opposed to them too. I think that a named type should bring with it some degree of type safety.

That's kinda the point surely, it does introduce a degree of type safety? If you want to use an arbitrary value you have to intentionally cast it to the enum; it encourages the general case to use the values provided by the string enum, but is open enough to allow other types.

you should use DU’s if they exist in a language, which provide compile time checks for exhaustiveness and extensibility via adding methods to type and new cases.

This is problematic because of layering, many of these enums would be defined in the BCL; however if you want to add another value you are then locked out. e.g. one of the other examples provided is HashAlgorithmName which in its definition is:

Asymmetric algorithms implemented using other technologies:

  • Must recognize at least "MD5", "SHA1", "SHA256", "SHA384", and "SHA512".
  • Should recognize additional CNG identifiers for any additional hash algorithms that they support.

So that would be an enum of type

public enum HashAlgorithmName : string
{
    Md5 = "MD5",
    Sha1 = "SHA1",
    Sha256 = "SHA256",
    Sha384 = "SHA384",
    Sha512 = "SHA512"
}

The current situation of just accepting arbitrary strings doesn't provide any intention to the parameter or guidance to what the parameter should be via the compiler; which enum strings would provide.

@HaloFour
Copy link
Contributor

HaloFour commented Oct 2, 2019

@benaadams

That's kinda the point surely, it does introduce a degree of type safety? If you want to use an arbitrary value you have to intentionally cast it to the enum

IMO those two statements are in direct contradiction to one another. I find the current behavior of (non-flags) enums to be pretty appalling and results in the compiler to be forced to treat any arbitrary integral value as a potential value of that enum. That makes about as much sense as having to treat any arbitrary combination of bits in a bool as something other than true or false.

If the goal is to provide guidance to the user as to common or suggested values for a given parameter I think a better approach would be via attribute and IDE support which wouldn't require any language changes and would also work across any language in the ecosystem.

Otherwise this feature seems to be offering a new type while encouraging users to pass invalid values of that type to methods.

@JCKodel
Copy link

JCKodel commented Aug 16, 2023

Just copy enums from Dart: https://dart.dev/language/enums and make them have properties, methods, support inheritance, etc.

enum NativePlatform {
  android("a"),
  ios("i"),
  windows("w"),
  macos("m"),
  linux("l"),
  web("b"),
  unknown("-");

  const NativePlatform(this.value);

  final String value;

  T when<T>({
    T Function()? onAndroid,
    T Function()? oniOS,
    T Function()? onWindows,
    T Function()? onMacOS,
    T Function()? onLinux,
    T Function()? onWeb,
    T Function()? orElse,
  }) {
    if (onAndroid == null &&
        oniOS == null &&
        onWindows == null &&
        onMacOS == null &&
        onLinux == null &&
        onWeb == null) {
      throw UnsupportedError("At least one NativePlatform should be provided");
    }

    if (onAndroid == null ||
        oniOS == null ||
        onWindows == null ||
        onMacOS == null ||
        onLinux == null ||
        onWeb == null) {
      if (orElse == null) {
        throw UnsupportedError(
            "If not all NativePlatforms are provided, orElse should be provided");
      }
    }

    switch (this) {
      case NativePlatform.android:
        return (onAndroid ?? orElse)!();
      case NativePlatform.ios:
        return (oniOS ?? orElse)!();
      case NativePlatform.windows:
        return (onWindows ?? orElse)!();
      case NativePlatform.macos:
        return (onMacOS ?? orElse)!();
      case NativePlatform.linux:
        return (onLinux ?? orElse)!();
      case NativePlatform.web:
        return (onWeb ?? orElse)!();
      case NativePlatform.unknown:
        return orElse!();
    }
  }
}

@HaloFour
Copy link
Contributor

Just copy enums from Dart

Object enums in general haven't been on the table because Sun/Oracle holds a patent on them, although that is set to expire soon.

It's likely that DUs will shape up similar to object enums, except also supporting additional data elements. More like Swift's or Rust's implementation of enums.

@Ai-N3rd
Copy link
Contributor

Ai-N3rd commented Feb 8, 2024

I would love to see this come to life. I've found myself wanting to do this more times than I can count.

@malix0
Copy link

malix0 commented Feb 22, 2024

The patent cited by @HaloFour is just expired https://patents.google.com/patent/US7263687B2/en

@HaloFour
Copy link
Contributor

The patent was never an impediment of this proposal (specifically string enums), but rather general purpose object enums where each case is a singleton instance.

With the team already dedicating a working group on discriminated and type unions I still feel that is the better route, as they cover both all of the capabilities of object enums and then some. It's also my belief that discriminated unions would handle the concept of "string enums" just fine.

@TahirAhmadov
Copy link

TahirAhmadov commented Feb 22, 2024

With the team already dedicating a working group on discriminated and type unions I still feel that is the better route, as they cover both all of the capabilities of object enums and then some.It's also my belief that discriminated unions would handle the concept of "string enums" just fine.

With all due respect, I'm not very satisfied with words like "feel" and "belief". For example, if we take the example below (similar to what OP has):

public enum OSPlatform : string
{
    FreeBSD = "Free BSD",
    Linux,
    Windows
}
var p = OSPlatform.Linux;
p = (OSPlatform)"Apple Toaster";
Console.Write(p); // outputs "Apple Toaster"

switch(p)
{
  case OSPlatform.Windows: .... break; // works - because these are constants
  case (OSPlatform)"Apple Toaster": .... break;
  default: ... break;
}

[SomeAttibute(OSPlatform.Linux)] // works - because these are constants
void Foo(OSPlatform p = OSPlatform.Linux) { ... } // works - because these are constants

class JsonWriter
{
  // very easy to add method by JSON library authors (ditto for XML serialization, etc.)
  public void Value<T>(T stringEnum) where T : StringEnum
  {
    this.Value(stringEnum.ToString());
  }
}
var jw = new JsonWriter(...);
jw.Value(p);

class Dto
{
  public OSPlatform OSPlatform { get; set; } // trivial to add recognition of all StringEnum types
}
JsonSerializer.Serialize(jw, new Dto()); // to JSON serializers

class SomeDataEntity
{
  public OSPlatform OSPlatform { get; set; } // trivially mapped with one generic converter in your favorite ORM
}
var entity = SomeORM.Query<SomeDataEntity>().First(); // no need to do anything for each StringEnum type

How are all of these accomplished with DUs? I think people who want to get string enums (including me) would like to see concrete code examples of all the above scenarios and not just vague assurances.
Thanks in advance.

@HaloFour
Copy link
Contributor

How are all of these accomplished with DUs?

DUs can have data elements, and one case can be "other" to cover any unknown cases at the time the DU was defined:

public struct enum Color {
    Red,
    Blue,
    Green,
    Other(byte r, byte g, byte b);
}

Depending on how the members on the DU are defined can blur whether you need to deal with the individual cases or can work with R/B/G values directly, etc.

As for serialization, that also depends on how the DUs are defined. I'd expect some degree of support out of the box, but if you want special handling that should also be possible. This isn't uncommon in Java object enums where JSON serialization uses a data element instead of the case name. This could be established via convention (e.g. Decodable in Swift) or via custom serializers, per use case:

enum OrderStatus: Decodable {
    case pending, approved, shipped, delivered, cancelled
    case unknown(value: String)
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let status = try? container.decode(String.self)
        switch status {
              case "Pending": self = .pending
              case "Approved": self = .approved
              case "Shipped": self = .shipped
              case "Delivered": self = .delivered
              case "Cancelled": self = .cancelled
              default:
                 self = .unknown(value: status ?? "unknown")
          }
      }
}

I also think behavior like "string enums" as proposed here might fall out of extensions:

public implicit extension OSPlatform for string {
    public static readonly OSPlatform FreeBSD = "Free BSD";
    public static readonly OSPlatform Linux = nameof(Linux);
    public static readonly OSPlatform Windows = nameof(Windows);
}

@TahirAhmadov
Copy link

I'm not sure how the Color and OrderStatus examples are relevant to the string enum conversation.
Regarding using extensions, I would think it would be explicit extensions, not implicit ones. It does make some scenarios easy, but it also completely fails to address other scenarios:

switch(p)
{
  case OSPlatform.Windows: .... break; // error - constant expected
}
[SomeAttibute(OSPlatform.Linux)] // error - constant expected
void Foo(OSPlatform p = OSPlatform.Linux) { ... } // error - constant expected

@HaloFour
Copy link
Contributor

@TahirAhmadov

I'm not sure how the Color and OrderStatus examples are relevant to the string enum conversation.

You asked how DUs address these kinds of cases. I'm showing you that languages that have DUs do use them to address these cases.

Regarding using extensions, I would think it would be explicit extensions, not implicit ones. It does make some scenarios easy, but it also completely fails to address other scenarios

That's an option. It's possible that the values could be declared as const also, since the extension type is erased.

@TahirAhmadov
Copy link

You asked how DUs address these kinds of cases. I'm showing you that languages that have DUs do use them to address these cases.

You cited DUs which address other scenarios, not string enums. That's why I asked to see what a DU representing specifically the OSPlatform type would look like.

That's an option. It's possible that the values could be declared as const also, since the extension type is erased.

That's actually an interesting idea. If const is allowed with extensions in this way, it does basically the same thing that "string enums" would the way it's envisioned by me and their other proponents.

public explicit extension OSPlatform for string {
    public const OSPlatform FreeBSD = (OSPlatform)"Free BSD"; // is casting required?
    public const OSPlatform Linux = nameof(Linux);
    public const OSPlatform Windows = nameof(Windows);
}

It's still more ceremony than desirable, especially if casting is needed, but I can live with it.

@TahirAhmadov
Copy link

Regarding explicit extension approach, there is another question:

// how do we represent this:
void Foo<T>(T stringEnum) where T : StringEnum { }
void Foo(StringEnum stringEnum) { }
// using the explicit extension approach?
void Foo<T>(T stringEnum) where T : string extension { }

1, is the above a useful feature, 2, what would the syntax look like?

@KennethHoff
Copy link

I also think behavior like "string enums" as proposed here might fall out of extensions:

public implicit extension OSPlatform for string {
   public static readonly OSPlatform FreeBSD = "Free BSD";
   public static readonly OSPlatform Linux = nameof(Linux);
   public static readonly OSPlatform Windows = nameof(Windows);
}

As was discussed in #7771, that does not seem to be the case:

This encoding scheme means that methods cannot be overloaded by different extensions for the same underlying type.

@HaloFour
Copy link
Contributor

@KennethHoff

That does not seem to be the case:

Yes, if the encoding uses erasure as described there that would mean that you couldn't overload a method between string and OSPlatform, but if you could pass arbitrary string values as an OSPlatform is that really necessary?

@TahirAhmadov
Copy link

@KennethHoff

That does not seem to be the case:

Yes, if the encoding uses erasure as described there that would mean that you couldn't overload a method between string and OSPlatform, but if you could pass arbitrary string values as an OSPlatform is that really necessary?

It may be necessary in those cases where multiple overloads exist which do different things for a string and a "string enum". The strong typing aspect of enums is valuable despite their "openness".
However it's possible that this use case adds to the list of reasons to make explicit extensions become a different type for the purposes of overload resolution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests