Jak zdefiniować wyliczenie z wartością ciągu?


98

Próbuję zdefiniować Enumi dodać prawidłowe wspólne separatory używane w plikach CSV lub podobnych. Następnie mam zamiar powiązać go z a ComboBoxjako źródłem danych, więc za każdym razem, gdy dodam lub usuwam definicję Enum, nie muszę niczego zmieniać w polu kombi.

Problem polega na tym, jak mogę zdefiniować wyliczenie z reprezentacją ciągu, coś takiego:

public enum SeparatorChars{Comma = ",", Tab = "\t", Space = " "}


Odpowiedzi:


114

Nie możesz - wartości wyliczenia muszą być wartościami całkowitymi. Możesz użyć atrybutów, aby powiązać wartość ciągu z każdą wartością wyliczenia lub w tym przypadku, jeśli każdy separator jest pojedynczym znakiem, możesz po prostu użyć charwartości:

enum Separator
{
    Comma = ',',
    Tab = '\t',
    Space = ' '
}

(EDYCJA: tylko dla wyjaśnienia, nie można utworzyć charpodstawowego typu wyliczenia, ale można użyć charstałych do przypisania wartości całkowitej odpowiadającej każdej wartości wyliczenia. Podstawowym typem powyższego wyliczenia jest int).

Następnie metoda rozszerzenia, jeśli jej potrzebujesz:

public string ToSeparatorString(this Separator separator)
{
    // TODO: validation
    return ((char) separator).ToString();
}

Znak nie jest prawidłowy w wyliczeniach. Msdn: "Każdy typ wyliczenia ma typ bazowy, którym może być dowolny typ całkowity oprócz char."
dół do

9
@dowhilefor: Możesz jednak użyć literału char jako wartości , zgodnie z moją odpowiedzią. Przetestowałem to :)
Jon Skeet

ponieważ to wymaganie dotyczy plików, użytkownik może potrzebować separatora CRLF. Czy to zadziała również w tym przypadku?
Maheep,

Dzięki Jon, czy nie liczy się jako znak ?!
Saeid Yazdani

1
@ShaunLuttin: wyliczenia są po prostu „nazwanymi liczbami” - więc wyliczenie w postaci ciągu naprawdę nie pasuje do tego modelu.
Jon Skeet

83

O ile wiem, nie będziesz mógł przypisywać wartości łańcuchowych do wyliczenia. Możesz stworzyć klasę zawierającą stałe łańcuchowe.

public static class SeparatorChars
{
    public static String Comma { get { return ",";} } 
    public static String Tab { get { return "\t,";} } 
    public static String Space { get { return " ";} } 
}

10
Wadą tego podejścia, w przeciwieństwie do innych, jest to, że nie można ich wyliczyć bez zrobienia czegoś dodatkowego / specjalnego.
caesay

Nie pomaga to wymusić pewnych wartości w czasie kompilacji, ponieważ separatorjest teraz ciągiem (może to być dowolne) zamiast Separatortypu z ograniczonymi prawidłowymi wartościami.
ChickenFeet

74

Możesz to osiągnąć, ale będzie to wymagało trochę pracy.

  1. Zdefiniuj klasę atrybutu, która będzie zawierać wartość ciągu dla wyliczenia.
  2. Zdefiniuj metodę rozszerzenia, która zwróci wartość z atrybutu. Np. GetStringValue (ta wartość Enum) zwróci wartość atrybutu.
  3. Następnie możesz zdefiniować wyliczenie w ten sposób.
publiczny test wyliczenia: int {
    [StringValue ("a")]
    Foo = 1,
    [StringValue ("b")]
    Coś = 2        
} 
  1. Aby odzyskać wartość z Attrinbute Test.Foo.GetStringValue ();

Zobacz: wyliczenie z wartościami ciągów w C #


6
Wiem, że to stare, ale jest oczywiście unikalne i pozwala na użycie wyliczeń w kodzie i wartości ciągu w DB. Niesamowite
A_kat

1
Kolejny późny komentarz, ale to naprawdę genialne rozwiązanie
Alan

37

Aby uzyskać proste wyliczenie wartości ciągów (lub dowolnego innego typu):

public static class MyEnumClass
{
    public const string 
        MyValue1 = "My value 1",
        MyValue2 = "My value 2";
}

Stosowanie: string MyValue = MyEnumClass.MyValue1;


1
Chociaż nie jest to wyliczenie, myślę, że może to zapewnić najlepsze rozwiązanie tego, co próbuje zrobić użytkownik. Czasami najprostsze rozwiązanie jest najlepsze.
Zesty

31

Nie możesz tego zrobić z wyliczeniami, ale możesz to zrobić w ten sposób:

public static class SeparatorChars
{
    public static string Comma = ",";

    public static string Tab = "\t";

    public static string Space = " ";
}

1
+1 Chociaż uważam, że to dobre rozwiązanie, zmieniłbym nazwę klasy lub typ na znaki. Żeby być konsekwentnym.
dół do

Dzięki, czy możesz powiedzieć, co będzie odpowiednikiem comboBox.DataSource = Enum.GetValues(typeof(myEnum));w tym przypadku?
Saeid Yazdani

1
@ Sean87: Jeśli chcesz to mieć, przyjąłbym odpowiedź JonSkeets.
Fischermaen,

Myślę, że to prawie właściwa odpowiedź, ponieważ nie można jej używać wewnątrz switch-casebloków. Pola powinny być constw porządku. Ale nadal nie można na to poradzić, jeśli chcesz Enum.GetValues(typeof(myEnum)).
André Santaló

7
Użyłbym constzamiast static. Stałe są tylko do odczytu, a także statyczne i nie można ich przypisać w konstruktorach (chyba że pola tylko do odczytu).
Olivier Jacot-Descombes

12

Nie możesz, ponieważ wyliczenie może opierać się tylko na pierwotnym typie liczbowym. Możesz spróbować użyć Dictionaryzamiast tego:

Dictionary<String, char> separators = new Dictionary<string, char>
{
    {"Comma", ','}, 
    {"Tab",  '\t'}, 
    {"Space", ' '},
};

Alternatywnie możesz użyć Dictionary<Separator, char>lub Dictionary<Separator, string>gdzie Separatorjest normalnym wyliczeniem:

enum Separator
{
    Comma,
    Tab,
    Space
}

co byłoby nieco przyjemniejsze niż bezpośrednia obsługa strun.


12

Klasę, która emuluje zachowanie wyliczenia, ale używa stringzamiast tego, intmożna utworzyć w następujący sposób ...

public class GrainType
{
    private string _typeKeyWord;

    private GrainType(string typeKeyWord)
    {
        _typeKeyWord = typeKeyWord;
    }

    public override string ToString()
    {
        return _typeKeyWord;
    }

    public static GrainType Wheat = new GrainType("GT_WHEAT");
    public static GrainType Corn = new GrainType("GT_CORN");
    public static GrainType Rice = new GrainType("GT_RICE");
    public static GrainType Barley = new GrainType("GT_BARLEY");

}

Stosowanie...

GrainType myGrain = GrainType.Wheat;

PrintGrainKeyword(myGrain);

następnie...

public void PrintGrainKeyword(GrainType grain) 
{
    Console.Writeline("My Grain code is " + grain.ToString());   // Displays "My Grain code is GT_WHEAT"
}

Jedyne, czego nie możesz zrobić GrainType myGrain = "GT_CORN", na przykład.
colmde,

1
mógłbyś, gdybyś przesadził z operatorem
SSX-SL33PY

9

Trochę za późno na odpowiedź, ale może to komuś pomoże w przyszłości. Okazało się, że łatwiej jest używać struktury struct do tego rodzaju problemów.

Poniższy przykład jest skopiowany z wklejonej części z kodu MS:

namespace System.IdentityModel.Tokens.Jwt
{
    //
    // Summary:
    //     List of registered claims from different sources http://tools.ietf.org/html/rfc7519#section-4
    //     http://openid.net/specs/openid-connect-core-1_0.html#IDToken
    public struct JwtRegisteredClaimNames
    {
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Actort = "actort";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Typ = "typ";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Sub = "sub";
        //
        // Summary:
        //     http://openid.net/specs/openid-connect-frontchannel-1_0.html#OPLogout
        public const string Sid = "sid";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Prn = "prn";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nbf = "nbf";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string Nonce = "nonce";
        //
        // Summary:
        //     http://tools.ietf.org/html/rfc7519#section-4
        public const string NameId = "nameid";

    }
}

Czy mógłbyś wyjaśnić, dlaczego to podejście jest lepsze niż użycie klasy?
Gerardo Grignoli

@GerardoGrignoli Nie wiem dokładnie, dlaczego używają struktury zamiast klasy w MS do tego rodzaju rzeczy. Nawet nie próbowałem się dowiedzieć, ponieważ to działa idealnie dla mnie. Może spróbuj tutaj zadać pytanie na stosie ...
suchoss

8

Może jest już za późno, ale oto jest.

Możemy użyć atrybutu EnumMember do zarządzania wartościami Enum.

public enum EUnitOfMeasure
{
    [EnumMember(Value = "KM")]
    Kilometer,
    [EnumMember(Value = "MI")]
    Miles
}

W ten sposób wartością wyniku dla EUnitOfMeasure będzie KM lub MI. Widać to również w odpowiedzi Andrew Whitakera .


5

Dla osób przybywających tutaj i szukających odpowiedzi na bardziej ogólne pytanie, możesz rozszerzyć koncepcję klasy statycznej, jeśli chcesz, aby kod wyglądał jak enum .

Poniższe podejście działa, gdy nie sfinalizowałeś tego, enum namesczego chcesz, a enum valuesstringreprezentacją enam name; użyj, nameof()aby uprościć refaktoryzację.

public static class Colours
{
    public static string Red => nameof(Red);
    public static string Green => nameof(Green);
    public static string Blue => nameof(Blue);
}

Osiąga to intencję wyliczenia, które ma wartości ciągów (takie jak następujący pseudokod):

public enum Colours
{
    "Red",
    "Green",
    "Blue"
}

4

Utworzyłem klasę bazową do tworzenia wyliczeń o wartościach ciągów w .NET. To tylko jeden plik C #, który możesz skopiować i wkleić do swoich projektów lub zainstalować za pośrednictwem pakietu NuGet o nazwie StringEnum .

Stosowanie:

///<completionlist cref="HexColor"/> 
class HexColor : StringEnum<HexColor>
{
    public static readonly HexColor Blue = New("#FF0000");
    public static readonly HexColor Green = New("#00FF00");
    public static readonly HexColor Red = New("#000FF");
}

cechy

  • Twoje StringEnum wygląda trochę podobnie do zwykłego wyliczenia:
    // Static Parse Method
    HexColor.Parse("#FF0000") // => HexColor.Red
    HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.Parse("invalid") // => throws InvalidOperationException

    // Static TryParse method.
    HexColor.TryParse("#FF0000") // => HexColor.Red
    HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.TryParse("invalid") // => null

    // Parse and TryParse returns the preexistent instances
    object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true

    // Conversion from your `StringEnum` to `string`
    string myString1 = HexColor.Red.ToString(); // => "#FF0000"
    string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)
  • Intellisense zasugeruje nazwę wyliczenia, jeśli klasa jest opatrzona komentarzem xml <completitionlist>. (Działa w C # i VB): ie

Demo Intellisense

Instalacja

Zarówno:

  • Zainstaluj najnowszy pakiet StringEnum NuGet, który jest oparty na .Net Standard 1.0tym, że działa na .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6 itd.
  • Lub wklej następującą klasę bazową StringEnum do projektu. ( najnowsza wersja )
    public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
    {
        protected string Value;
        private static IList<T> valueList = new List<T>();
        protected static T New(string value)
        {
            if (value == null)
                return null; // the null-valued instance is null.

            var result = new T() { Value = value };
            valueList.Add(result);
            return result;
        }

        public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
        public override string ToString() => Value;

        public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
        public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;

        public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
        bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
        public override int GetHashCode() => Value.GetHashCode();

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case sensitivity.</param>
        public static T Parse(string value, bool caseSensitive = false)
        {
            var result = TryParse(value, caseSensitive);
            if (result == null)
                throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");

            return result;
        }

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case sensitivity.</param>
        public static T TryParse(string value, bool caseSensitive = false)
        {
            if (value == null) return null;
            if (valueList.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
            var field = valueList.FirstOrDefault(f => f.Value.Equals(value,
                    caseSensitive ? StringComparison.Ordinal
                                  : StringComparison.OrdinalIgnoreCase));
            // Not using InvariantCulture because it's only supported in NETStandard >= 2.0

            if (field == null)
                return null;

            return field;
        }
    }
  • Aby uzyskać Newtonsoft.Jsonobsługę serializacji, skopiuj zamiast tego tę rozszerzoną wersję. StringEnum.cs

Zrozumiałem po tym, że ten kod jest podobny do odpowiedzi Bena. Szczerze napisałem to od zera. Jednak myślę, że ma kilka dodatków, takich jak <completitionlist>hack, wynikowa klasa wygląda bardziej jak Enum, bez użycia refleksji na temat Parse (), pakietu NuGet i repozytorium, gdzie mam nadzieję, że rozwiążę nadchodzące problemy i opinie.


3

Opierając się na niektórych odpowiedziach, zaimplementowałem klasę bazową wielokrotnego użytku, która naśladuje zachowanie wyliczenia, ale stringjako typ bazowy. Obsługuje różne operacje, w tym:

  1. uzyskanie listy możliwych wartości
  2. konwertowanie na ciąg
  3. Porównanie z innych przypadkach przez .Equals, ==i!=
  4. konwersja do / z JSON przy użyciu JSON.NET JsonConverter

To jest cała klasa bazowa:

public abstract class StringEnumBase<T> : IEquatable<T>
    where T : StringEnumBase<T>
{
    public string Value { get; }

    protected StringEnumBase(string value) => this.Value = value;

    public override string ToString() => this.Value;

    public static List<T> AsList()
    {
        return typeof(T)
            .GetProperties(BindingFlags.Public | BindingFlags.Static)
            .Where(p => p.PropertyType == typeof(T))
            .Select(p => (T)p.GetValue(null))
            .ToList();
    }

    public static T Parse(string value)
    {
        List<T> all = AsList();

        if (!all.Any(a => a.Value == value))
            throw new InvalidOperationException($"\"{value}\" is not a valid value for the type {typeof(T).Name}");

        return all.Single(a => a.Value == value);
    }

    public bool Equals(T other)
    {
        if (other == null) return false;
        return this.Value == other?.Value;
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        if (obj is T other) return this.Equals(other);
        return false;
    }

    public override int GetHashCode() => this.Value.GetHashCode();

    public static bool operator ==(StringEnumBase<T> a, StringEnumBase<T> b) => a?.Equals(b) ?? false;

    public static bool operator !=(StringEnumBase<T> a, StringEnumBase<T> b) => !(a?.Equals(b) ?? false);

    public class JsonConverter<T> : Newtonsoft.Json.JsonConverter
        where T : StringEnumBase<T>
    {
        public override bool CanRead => true;

        public override bool CanWrite => true;

        public override bool CanConvert(Type objectType) => ImplementsGeneric(objectType, typeof(StringEnumBase<>));

        private static bool ImplementsGeneric(Type type, Type generic)
        {
            while (type != null)
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == generic)
                    return true;

                type = type.BaseType;
            }

            return false;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken item = JToken.Load(reader);
            string value = item.Value<string>();
            return StringEnumBase<T>.Parse(value);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value is StringEnumBase<T> v)
                JToken.FromObject(v.Value).WriteTo(writer);
        }
    }
}

A tak można zaimplementować swoje „wyliczenie ciągu”:

[JsonConverter(typeof(JsonConverter<Colour>))]
public class Colour : StringEnumBase<Colour>
{
    private Colour(string value) : base(value) { }

    public static Colour Red => new Colour("red");
    public static Colour Green => new Colour("green");
    public static Colour Blue => new Colour("blue");
}

Które można wykorzystać w ten sposób:

public class Foo
{
    public Colour colour { get; }

    public Foo(Colour colour) => this.colour = colour;

    public bool Bar()
    {
        if (this.colour == Colour.Red || this.colour == Colour.Blue)
            return true;
        else
            return false;
    }
}

Mam nadzieję, że ktoś uzna to za przydatne!


2

Cóż, najpierw spróbuj przypisać łańcuchy, a nie znaki, nawet jeśli są to tylko jeden znak. użyj „,” zamiast „,”. Następną rzeczą jest to, że wyliczenia przyjmują tylko typy całkowite bez charmożliwości użycia wartości Unicode, ale zdecydowanie radzę ci tego nie robić. Jeśli masz pewność, że te wartości pozostają takie same, w różnych kulturach i językach, użyłbym klasy statycznej z ciągami stałymi.


2

Chociaż naprawdę nie jest możliwe użycie a charlub a stringjako podstawy wyliczenia, myślę, że nie jest to to, co naprawdę lubisz robić.

Jak wspomniałeś, chciałbyś mieć wyliczenie możliwości i pokazać ich łańcuchową reprezentację w polu kombi. Jeśli użytkownik wybierze jedną z tych reprezentacji ciągu, chcesz uzyskać odpowiednie wyliczenie. A to jest możliwe:

Najpierw musimy połączyć jakiś ciąg z wartością wyliczenia. Można to zrobić, korzystając z metody DescriptionAttributeopisanej tutaj lub tutaj .

Teraz musisz utworzyć listę wartości wyliczeniowych i odpowiadających im opisów. Można to zrobić za pomocą następującej metody:

/// <summary>
/// Creates an List with all keys and values of a given Enum class
/// </summary>
/// <typeparam name="T">Must be derived from class Enum!</typeparam>
/// <returns>A list of KeyValuePair&lt;Enum, string&gt; with all available
/// names and values of the given Enum.</returns>
public static IList<KeyValuePair<T, string>> ToList<T>() where T : struct
{
    var type = typeof(T);

    if (!type.IsEnum)
    {
        throw new ArgumentException("T must be an enum");
    }

    return (IList<KeyValuePair<T, string>>)
            Enum.GetValues(type)
                .OfType<T>()
                .Select(e =>
                {
                    var asEnum = (Enum)Convert.ChangeType(e, typeof(Enum));
                    return new KeyValuePair<T, string>(e, asEnum.Description());
                })
                .ToArray();
}

Teraz będziesz mieć listę par klucz-wartość wszystkich wyliczeń i ich opis. Więc po prostu przypiszmy to jako źródło danych dla pola kombi.

var comboBox = new ComboBox();
comboBox.ValueMember = "Key"
comboBox.DisplayMember = "Value";
comboBox.DataSource = EnumUtilities.ToList<Separator>();

comboBox.SelectedIndexChanged += (sender, e) =>
{
    var selectedEnum = (Separator)comboBox.SelectedValue;
    MessageBox.Show(selectedEnum.ToString());
}

Użytkownik widzi wszystkie reprezentacje ciągów wyliczenia, a w kodzie otrzymasz żądaną wartość wyliczenia.


0

Nie możemy zdefiniować wyliczenia jako typu string. Zatwierdzone typy wyliczenia to bajt, sbyte, short, ushort, int, uint, long i ulong.

Jeśli potrzebujesz więcej szczegółów na temat wyliczania, skorzystaj z poniższego linku, który pomoże ci zrozumieć wyliczanie. Wyliczenie

@ narendras1414


0

Mi to pasuje..

   public class ShapeTypes
    {
        private ShapeTypes() { }
        public static string OVAL
        {
            get
            {
                return "ov";
            }
            private set { }
        }

        public static string SQUARE
        {
            get
            {
                return "sq";
            }
            private set { }
        }

        public static string RECTANGLE
        {
            get
            {
                return "rec";
            }
            private set { }
        }
    }

0

To, co ostatnio zacząłem, to używanie krotek

public static (string Fox, string Rabbit, string Horse) Animals = ("Fox", "Rabbit", "Horse");
...
public static (string Comma, string Tab, string Space) SeparatorChars = (",", "\t", " ");

-1

Klasa enumaracji

 public sealed class GenericDateTimeFormatType
    {

        public static readonly GenericDateTimeFormatType Format1 = new GenericDateTimeFormatType("dd-MM-YYYY");
        public static readonly GenericDateTimeFormatType Format2 = new GenericDateTimeFormatType("dd-MMM-YYYY");

        private GenericDateTimeFormatType(string Format)
        {
            _Value = Format;
        }

        public string _Value { get; private set; }
    }

Zużycie enumaracji

public static void Main()
{
       Country A = new Country();

       A.DefaultDateFormat = GenericDateTimeFormatType.Format1;

      Console.ReadLine();
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.