Как определить перечисление со строковым значением?


99

Я пытаюсь определить Enumи добавить действительные общие разделители, которые используются в CSV или подобных файлах. Затем я собираюсь привязать его к a ComboBoxв качестве источника данных, поэтому всякий раз, когда я добавляю или удаляю определение Enum, мне не нужно было ничего менять в поле со списком.

Проблема в том, как я могу определить enum со строковым представлением, например:

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


возможный дубликат
связывания

Ответы:


114

Вы не можете - значения перечисления должны быть целыми значениями. Вы можете использовать атрибуты, чтобы связать строковое значение с каждым значением перечисления, или в этом случае, если каждый разделитель представляет собой один символ, вы можете просто использовать charзначение:

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

(РЕДАКТИРОВАТЬ: Чтобы уточнить, вы не можете создать charбазовый тип перечисления, но вы можете использовать charконстанты для присвоения интегрального значения, соответствующего каждому значению перечисления. Базовым типом указанного выше перечисления является int.)

Затем метод расширения, если он вам нужен:

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

Char недопустим в перечислениях. Msdn: «Каждый тип перечисления имеет базовый тип, который может быть любым целым типом, кроме char».
dowhile за

9
@dowhilefor: вы можете использовать литерал char для значения , согласно моему ответу. Я тестировал :)
Джон Скит

поскольку это требование относится к файлам, пользователю может понадобиться разделитель CRLF. Будет ли это работать и в этом случае?
Maheep

Спасибо, Джон, \ t считается символом ?!
Саид Яздани

1
@ShaunLuttin: перечисления - это просто «именованные числа», поэтому строковое перечисление вообще не подходит для этой модели.
Джон Скит,

83

Насколько я знаю, вам не разрешат присваивать строковые значения enum. Что вы можете сделать, так это создать класс со строковыми константами в нем.

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

10
Обратной стороной этого подхода в отличие от других является то, что вы не можете перечислить их, не делая чего-то особенного.
Caesay

Это не помогает обеспечить соблюдение определенных значений во время компиляции, поскольку separatorтеперь это строка (может быть что угодно) вместо Separatorтипа с ограниченными допустимыми значениями.
ChickenFeet,

74

Вы можете этого добиться, но потребуется немного поработать.

  1. Определите класс атрибута, который будет содержать строковое значение для enum.
  2. Определите метод расширения, который вернет значение из атрибута. Например .. GetStringValue (это значение Enum) вернет значение атрибута.
  3. Затем вы можете определить перечисление вот так ...
public enum Test: int {
    [StringValue ("a")]
    Foo = 1,
    [StringValue ("b")]
    Что-то = 2        
} 
  1. Чтобы вернуть значение из Attrinbute Test.Foo.GetStringValue ();

См .: Enum со строковыми значениями в C #


6
Я знаю это старое, но оно, очевидно, уникально и позволяет использовать перечисления в коде и строковое значение в БД. Удивительно
A_kat

1
Еще один поздний комментарий, но это действительно гениальное решение
Алан

37

Для простого перечисления строковых значений (или любого другого типа):

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

Применение: string MyValue = MyEnumClass.MyValue1;


1
Хотя это не перечисление, я думаю, что это может стать лучшим решением того, что пытается сделать пользователь. Иногда самое простое решение оказывается лучшим.
Zesty

31

Вы не можете сделать это с помощью перечислений, но можете сделать это так:

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

    public static string Tab = "\t";

    public static string Space = " ";
}

1
+1 Хотя я думаю, что это правильное решение, я бы изменил имя класса или поменял тип на символы. Просто чтобы быть последовательным.
dowhile за

Спасибо, подскажите, что будет эквивалент comboBox.DataSource = Enum.GetValues(typeof(myEnum));в этом случае?
Саид Яздани

1
@ Sean87: Я бы хотел получить это, я бы взял ответ JonSkeets.
Fischermaen

Думаю, это почти правильный ответ, потому что внутри switch-caseблоков его нельзя использовать . Поля должны быть constв порядке. Но все равно ничего не поделаешь, если хочешь Enum.GetValues(typeof(myEnum)).
Андре Сантало

7
Я бы использовал constвместо static. Константы доступны только для чтения, а также статичны и не могут быть присвоены в конструкторах (кроме полей только для чтения).
Оливье Жако-Декомб

12

Вы не можете, потому что enum может быть основан только на примитивном числовом типе. DictionaryВместо этого вы можете попробовать использовать :

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

В качестве альтернативы вы можете использовать Dictionary<Separator, char>или Dictionary<Separator, string>где Separatorобычное перечисление:

enum Separator
{
    Comma,
    Tab,
    Space
}

что было бы немного приятнее, чем работать со строками напрямую.


12

Класс, который имитирует поведение перечисления, но использует stringвместо, intможет быть создан следующим образом ...

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");

}

Применение...

GrainType myGrain = GrainType.Wheat;

PrintGrainKeyword(myGrain);

тогда...

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

Единственное, что вы GrainType myGrain = "GT_CORN", например, не можете сделать .
Colmde 07

1
вы могли бы, если бы вы перебили оператора
SSX-SL33PY

9

Слишком поздно для ответа, но, может быть, это кому-то поможет в будущем. Мне было проще использовать struct для такого рода проблем.

Следующий образец - это скопированная часть из кода 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";

    }
}

Не могли бы вы объяснить, почему этот подход лучше, чем использование класса?
Херардо Гриньоли

@GerardoGrignoli Я точно не знаю, почему они используют структуру вместо класса в MS для такого рода вещей. Я даже не пытался это выяснить, так как у меня это отлично работает. Может быть, попробуй задать вопрос здесь, в стеке ...
suchoss

8

Может быть, уже слишком поздно, но вот оно.

Мы можем использовать атрибут EnumMember для управления значениями Enum.

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

Таким образом, значение результата для EUnitOfMeasure будет KM или MI. Это также можно увидеть в ответе Эндрю Уитакера .


5

Для людей, прибывающих сюда в поисках ответа на более общий вопрос, вы можете расширить концепцию статического класса, если хотите, чтобы ваш код выглядел как enum.

Следующий подход работает, когда вы еще не доработали то, enum namesчто хотите, и enum valuesявляетесь stringпредставлением enam name; используйте, nameof()чтобы упростить рефакторинг.

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

Этим достигается цель перечисления со строковыми значениями (например, следующий псевдокод):

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

4

Я создал базовый класс для создания перечислений со строковыми значениями в .NET. Это всего лишь один файл C #, который вы можете скопировать и вставить в свои проекты или установить через пакет NuGet с именем StringEnum .

Применение:

///<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");
}

Характеристики

  • Ваш StringEnum несколько похож на обычное перечисление:
    // 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 предложит имя перечисления, если класс аннотирован xml-комментарием <completitionlist>. (Работает как на C #, так и на VB): т.е.

Демо Intellisense

Установка

Либо:

  • Установите последний пакет StringEnum NuGet, на основе которого .Net Standard 1.0он работает на .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6 и т. Д.
  • Или вставьте в свой проект следующий базовый класс StringEnum. ( последняя версия )
    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;
        }
    }
  • Для Newtonsoft.Jsonподдержки сериализации вместо этого скопируйте эту расширенную версию. StringEnum.cs

Я понял после того, что этот код похож на ответ Бена. Я искренне написал это с нуля. Однако я думаю, что у него есть несколько дополнений, таких как <completitionlist>взлом, результирующий класс больше похож на Enum, без использования отражения в Parse (), пакете NuGet и репозитории, где я надеюсь решить входящие проблемы и отзывы.


3

Основываясь на некоторых ответах здесь, я реализовал многоразовый базовый класс, который имитирует поведение перечисления, но stringв качестве базового типа. Он поддерживает различные операции, в том числе:

  1. получение списка возможных значений
  2. преобразование в строку
  3. сравнение с другими экземплярами помощи .Equals, ==и!=
  4. преобразование в / из JSON с помощью JSON.NET JsonConverter

Это базовый класс в целом:

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);
        }
    }
}

Вот как бы вы реализовали свое «перечисление строк»:

[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");
}

Что можно было бы использовать так:

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;
    }
}

Надеюсь, кому-то это пригодится!


2

Сначала вы пытаетесь назначить строки, а не символы, даже если это всего лишь один символ. используйте ',' вместо ",". Далее, перечисления принимают только целые типы без charиспользования значения Unicode, но я настоятельно рекомендую вам не делать этого. Если вы уверены, что эти значения остаются неизменными в разных культурах и языках, я бы использовал статический класс с константными строками.


2

Хотя действительно невозможно использовать a charили a stringв качестве основы для перечисления, я думаю, что это не то, что вам действительно нравится.

Как вы упомянули, вы хотели бы иметь перечень возможностей и показать их строковое представление в поле со списком. Если пользователь выбирает одно из этих строковых представлений, вы хотите получить соответствующее перечисление. А это возможно:

Сначала мы должны связать некоторую строку со значением перечисления. Это можно сделать, используя то, DescriptionAttributeчто описано здесь или здесь .

Теперь вам нужно создать список значений перечисления и соответствующих описаний. Это можно сделать следующим способом:

/// <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();
}

Теперь у вас будет список пар ключ-значение всех перечислений и их описание. Так что давайте просто назначим это как источник данных для поля со списком.

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());
}

Пользователь видит все строковые представления перечисления, и в вашем коде вы получите желаемое значение перечисления.


0

Мы не можем определить перечисление как строковый. Утвержденные типы для перечисления: byte, sbyte, short, ushort, int, uint, long или ulong.

Если вам нужна более подробная информация о перечислении, перейдите по ссылке ниже, эта ссылка поможет вам разобраться в перечислении. Перечисление

@ narendras1414


0

Меня устраивает..

   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

Недавно я начал использовать кортежи.

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

-1

Enumaration Class

 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; }
    }

Enumaration Consuption

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

       A.DefaultDateFormat = GenericDateTimeFormatType.Format1;

      Console.ReadLine();
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.