Làm thế nào để xác định một enum với giá trị chuỗi?


97

Tôi đang cố gắng xác định Enumvà thêm dấu phân tách chung hợp lệ được sử dụng trong CSV hoặc các tệp tương tự. Sau đó, tôi sẽ liên kết nó với một ComboBoxnguồn dữ liệu để bất cứ khi nào tôi thêm hoặc xóa khỏi định nghĩa Enum, tôi sẽ không cần thay đổi bất kỳ điều gì trong hộp tổ hợp.

Vấn đề là làm thế nào tôi có thể định nghĩa enum với biểu diễn chuỗi, một cái gì đó như:

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


có thể trùng lặp của Liên kết enums với chuỗi trong C #
nawfal

Câu trả lời:


113

Bạn không thể - giá trị enum phải là giá trị tích phân. Bạn có thể sử dụng các thuộc tính để liên kết giá trị chuỗi với mỗi giá trị enum hoặc trong trường hợp này nếu mỗi dấu phân cách là một ký tự duy nhất, bạn có thể chỉ cần sử dụng chargiá trị:

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

(CHỈNH SỬA: Chỉ để làm rõ, bạn không thể tạo charkiểu cơ bản của enum, nhưng bạn có thể sử dụng charhằng số để gán giá trị tích phân tương ứng với mỗi giá trị enum. Kiểu cơ bản của enum ở trên là int.)

Sau đó, một phương thức mở rộng nếu bạn cần:

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

Char không hợp lệ trong enums. Msdn: "Mọi kiểu liệt kê đều có một kiểu cơ bản, có thể là bất kỳ kiểu tích phân nào ngoại trừ char."
làm từ

8
@dowhilefor: Bạn có thể sử dụng ký tự char cho giá trị , theo câu trả lời của tôi. Tôi đã thử nghiệm nó :)
Jon Skeet

vì yêu cầu này dành cho tệp người dùng có thể cần dấu phân tách CRLF. Nó sẽ hoạt động cho trường hợp đó?
Maheep

Cảm ơn Jon, không được tính là một char ?!
Saeid Yazdani

1
@ShaunLuttin: enum chỉ là "số được đặt tên" - vì vậy một chuỗi enum thực sự không phù hợp với mô hình đó chút nào.
Jon Skeet

83

Theo như tôi biết, bạn sẽ không được phép gán giá trị chuỗi cho enum. Những gì bạn có thể làm là tạo một lớp với các hằng số chuỗi trong đó.

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

9
Mặt trái của cách tiếp cận này trái ngược với những người khác là bạn không thể liệt kê những điều này mà không làm điều gì đó bổ sung / đặc biệt.
caesay

Điều này không giúp thực thi các giá trị nhất định trong thời gian biên dịch, vì separatorbây giờ là một chuỗi (có thể là bất kỳ thứ gì) thay vì một Separatorkiểu có các giá trị hợp lệ bị hạn chế.
ChickenFeet

73

Bạn có thể đạt được nó nhưng sẽ cần một chút công việc.

  1. Xác định một lớp thuộc tính sẽ chứa giá trị chuỗi cho enum.
  2. Xác định một phương thức mở rộng sẽ trả về giá trị từ thuộc tính. Vd..GetStringValue (giá trị Enum này) sẽ trả về giá trị thuộc tính.
  3. Sau đó, bạn có thể định nghĩa enum như thế này ..
public enum Test: int {
    [StringValue ("a")]
    Foo = 1,
    [StringValue ("b")]
    Cái gì đó = 2        
} 
  1. Để lấy lại giá trị từ Attrinbute Test.Foo.GetStringValue ();

Tham khảo: Enum Với Giá trị Chuỗi Trong C #


5
Tôi biết điều này cũ nhưng nó rõ ràng là duy nhất và cho phép bạn sử dụng enum trong mã và giá trị chuỗi trong DB. Tuyệt vời
A_kat

1
Một nhận xét muộn khác, nhưng đây thực sự là một giải pháp tuyệt vời
Alan

36

Đối với một enum giá trị chuỗi đơn giản (hoặc bất kỳ kiểu nào khác):

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

Sử dụng: string MyValue = MyEnumClass.MyValue1;


1
Mặc dù đây không phải là một enum, nhưng tôi nghĩ rằng điều này có thể cung cấp giải pháp tốt nhất cho những gì người dùng đang cố gắng thực hiện. Đôi khi, giải pháp đơn giản nhất là tốt nhất.
Zesty

29

Bạn không thể làm điều này với enums, nhưng bạn có thể làm như vậy:

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

    public static string Tab = "\t";

    public static string Space = " ";
}

1
+1 Mặc dù tôi nghĩ đó là giải pháp phù hợp, nhưng tôi sẽ thay đổi tên của lớp hoặc thay đổi kiểu thành ký tự. Chỉ để nhất quán.
khi

Cảm ơn, bạn có thể cho biết những gì sẽ tương đương với comboBox.DataSource = Enum.GetValues(typeof(myEnum));trong trường hợp này?
Saeid Yazdani

1
@ Sean87: Tôi bạn muốn có điều đó, tôi sẽ trả lời JonSkeets.
Fischermaen

Tôi nghĩ rằng đây gần như là câu trả lời đúng, bởi vì nó không thể sử dụng được bên trong switch-casecác khối. Các trường phải consttheo thứ tự. Nhưng nếu muốn vẫn không được Enum.GetValues(typeof(myEnum)).
André Santaló

7
Tôi sẽ sử dụng constthay vì static. Hằng số là chỉ đọc cũng như tĩnh và không thể chuyển đổi trong các hàm tạo (trừ khi các trường chỉ đọc).
Olivier Jacot-Descombes

12

Bạn không thể, bởi vì enum chỉ có thể dựa trên một kiểu số nguyên thủy. DictionaryThay vào đó, bạn có thể thử sử dụng :

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

Ngoài ra, bạn có thể sử dụng enum bình thường Dictionary<Separator, char>hoặc Dictionary<Separator, string>ở đâu Separator:

enum Separator
{
    Comma,
    Tab,
    Space
}

sẽ dễ chịu hơn một chút so với việc xử lý các chuỗi trực tiếp.


11

Một lớp mô phỏng hành vi enum nhưng sử dụng stringthay vì intcó thể được tạo như sau ...

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

}

Sử dụng...

GrainType myGrain = GrainType.Wheat;

PrintGrainKeyword(myGrain);

sau đó...

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

Điều duy nhất là bạn không thể làm GrainType myGrain = "GT_CORN", chẳng hạn.
colmde

bạn có thể nếu bạn
ghi đè

8

Đó là một câu trả lời muộn, nhưng có thể nó sẽ giúp ích cho ai đó trong tương lai. Tôi thấy việc sử dụng struct cho loại vấn đề này dễ dàng hơn.

Mẫu sau là sao chép phần được dán từ mã 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";

    }
}

Bạn có thể vui lòng giải thích tại sao aprroach này tốt hơn so với sử dụng một lớp?
Gerardo Grignoli

@GerardoGrignoli Tôi không biết chính xác tại sao họ sử dụng struct thay vì class trong MS cho loại điều này. Tôi thậm chí đã không cố gắng tìm hiểu, vì điều này hoàn toàn phù hợp với tôi. Có thể thử đặt câu hỏi ở đây trên đống ...
suchoss

6

Có lẽ đã quá muộn, nhưng đây rồi.

Chúng ta có thể sử dụng thuộc tính EnumMember để quản lý các giá trị Enum.

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

Bằng cách này, giá trị kết quả cho EUnitOfMeasure sẽ là KM hoặc MI. Điều này cũng có thể được nhìn thấy trong câu trả lời của Andrew Whitaker .


5

Đối với những người đến đây tìm kiếm câu trả lời cho một câu hỏi chung chung hơn, bạn có thể mở rộng khái niệm lớp tĩnh nếu bạn muốn mã của mình trông giống như một enum.

Cách tiếp cận sau hoạt động khi bạn chưa hoàn thành những enum namesgì bạn muốn và đó enum valueslà sự stringthể hiện của enam name; sử dụng nameof()để làm cho việc tái cấu trúc của bạn đơn giản hơn.

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

Điều này đạt được mục đích của một enum có các giá trị chuỗi (chẳng hạn như mã giả sau):

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

4

Tôi đã tạo một lớp cơ sở để tạo các enum có giá trị chuỗi trong .NET. Nó chỉ là một tệp C # mà bạn có thể sao chép và dán vào các dự án của mình hoặc cài đặt thông qua gói NuGet có tên StringEnum .

Sử dụng:

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

Đặc trưng

  • StringEnum của bạn trông hơi giống với một enum thông thường:
    // 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 sẽ gợi ý tên enum nếu lớp được chú thích bằng chú thích xml <completitionlist>. (Hoạt động trong cả C # và VB): tức là

Bản demo Intellisense

Cài đặt

Hoặc:

  • Cài đặt gói StringEnum NuGet mới nhất , được dựa trên .Net Standard 1.0để nó chạy trên .Net Core> = 1.0, .Net Framework> = 4.5,Mono > = 4.6, v.v.
  • Hoặc dán lớp cơ sở StringEnum sau vào dự án của bạn. ( phiên bản mới nhất )
    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được hỗ trợ tuần tự hóa, hãy sao chép phiên bản mở rộng này để thay thế. StringEnum.cs

Sau thực tế, tôi nhận ra rằng đoạn mã này giống với câu trả lời của Ben. Tôi chân thành viết nó từ đầu. Tuy nhiên, tôi nghĩ rằng nó có một số tính năng bổ sung, chẳng hạn như <completitionlist>hack, lớp kết quả trông giống Enum hơn, không sử dụng phản chiếu trên Parse (), gói NuGet và repo nơi tôi hy vọng sẽ giải quyết các vấn đề và phản hồi đến.


3

Dựa trên một số câu trả lời ở đây, tôi đã triển khai một lớp cơ sở có thể tái sử dụng bắt chước hành vi của một enum nhưng với stringdưới dạng kiểu cơ bản. Nó hỗ trợ các hoạt động khác nhau bao gồm:

  1. nhận danh sách các giá trị có thể có
  2. chuyển đổi thành chuỗi
  3. so sánh với các trường hợp khác thông qua .Equals, ==!=
  4. chuyển đổi sang / từ JSON bằng JSON.NET JsonConverter

Đây là toàn bộ lớp cơ sở của nó:

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

Và đây là cách bạn triển khai "string enum" của mình:

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

Có thể được sử dụng như thế này:

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

Tôi hi vọng ai đó thấy nó hữu ích!


2

Trước tiên, bạn cố gắng gán các chuỗi không phải ký tự, ngay cả khi chúng chỉ là một ký tự. sử dụng ',' thay vì ",". Điều tiếp theo là, enum chỉ lấy các kiểu tích phân mà charbạn không thể sử dụng giá trị unicode, nhưng tôi thực sự khuyên bạn không nên làm như vậy. Nếu bạn chắc chắn rằng các giá trị này không đổi, ở các nền văn hóa và ngôn ngữ khác nhau, tôi sẽ sử dụng một lớp tĩnh với chuỗi const.


2

Mặc dù thực sự không thể sử dụng a charhoặc a stringlàm cơ sở cho enum, nhưng tôi nghĩ đây không phải là điều bạn thực sự thích làm.

Giống như bạn đã đề cập, bạn muốn có một loạt các khả năng và hiển thị biểu diễn chuỗi của điều này trong một hộp tổ hợp. Nếu người dùng chọn một trong các biểu diễn chuỗi này, bạn muốn lấy ra enum tương ứng. Và điều này có thể xảy ra:

Đầu tiên, chúng ta phải liên kết một số chuỗi với một giá trị enum. Điều này có thể được thực hiện bằng cách sử dụng DescriptionAttributenhư được mô tả ở đây hoặc ở đây .

Bây giờ bạn cần tạo một danh sách các giá trị enum và các mô tả tương ứng. Điều này có thể được thực hiện bằng cách sử dụng phương pháp sau:

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

Bây giờ bạn sẽ có một danh sách các cặp giá trị khóa của tất cả các enum và mô tả của chúng. Vì vậy, chúng ta hãy chỉ định đây làm nguồn dữ liệu cho một hộp tổ hợp.

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

Người dùng thấy tất cả các biểu diễn chuỗi của enum và trong mã của bạn, bạn sẽ nhận được giá trị enum mong muốn.


0

Chúng tôi không thể định nghĩa kiểu liệt kê là kiểu chuỗi. Các kiểu được chấp thuận cho một enum là byte, sbyte, short, ushort, int, uint, long, hoặc ulong.

Nếu bạn cần biết thêm chi tiết về cách liệt kê, vui lòng theo liên kết dưới đây, liên kết đó sẽ giúp bạn hiểu rõ về cách liệt kê. Sự liệt kê

@ narendras1414


0

Nó phù hợp với tôi ..

   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

Những gì tôi đã bắt đầu làm gần đây là sử dụng Tuples

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

-1

Lớp gia nhập

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

Kết nạp

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

       A.DefaultDateFormat = GenericDateTimeFormatType.Format1;

      Console.ReadLine();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.