เราสามารถกำหนดการแปลงโดยนัยของ enums ใน c # ได้หรือไม่?


129

เป็นไปได้หรือไม่ที่จะกำหนดการแปลง enums โดยปริยายใน c #

สิ่งที่สามารถบรรลุนี้?

public enum MyEnum
{
    one = 1, two = 2
}

MyEnum number = MyEnum.one;
long i = number;

ถ้าไม่เพราะเหตุใด


2
ฉันก็อยากทำเช่นนี้เหมือนกัน เรามี enum enum YesNo {Yes, No}ที่สามารถแปลงเป็น bool ได้โดยปริยาย
พันเอก Panic

สังเกตว่าแนวคิดนี้ปิดใช้งานการตรวจสอบความปลอดภัยประเภทคอมไพเลอร์ ในระยะยาวการแปลงชวเลขที่ชัดเจนเช่นการต่อท้าย "~" อาจจะดีกว่า
crokusek

ลิงก์ใช้ไม่ได้อีกต่อไปเราสามารถลบลิงก์หรือโพสต์เว็บไซต์ใหม่ที่ไหนสักแห่งได้ไหม
ワイきんぐ

คำตอบ:


128

มีวิธีแก้ไข. พิจารณาสิ่งต่อไปนี้:

public sealed class AccountStatus
{
    public static readonly AccountStatus Open = new AccountStatus(1);
    public static readonly AccountStatus Closed = new AccountStatus(2);

    public static readonly SortedList<byte, AccountStatus> Values = new SortedList<byte, AccountStatus>();
    private readonly byte Value;

    private AccountStatus(byte value)
    {
        this.Value = value;
        Values.Add(value, this);
    }


    public static implicit operator AccountStatus(byte value)
    {
        return Values[value];
    }

    public static implicit operator byte(AccountStatus value)
    {
        return value.Value;
    }
}

ข้างต้นเสนอการแปลงโดยปริยาย:

        AccountStatus openedAccount = 1;            // Works
        byte openedValue = AccountStatus.Open;      // Works

นี่เป็นงานที่ดีกว่าการประกาศ enum ปกติเล็กน้อย (แม้ว่าคุณสามารถ refactor บางส่วนข้างต้นเป็นคลาสพื้นฐานทั่วไปได้) คุณสามารถไปได้ไกลกว่านี้โดยให้คลาสพื้นฐานใช้ IComparable & IEquatable รวมถึงเพิ่มวิธีการคืนค่าของ DescriptionAttributes ชื่อที่ประกาศ ฯลฯ ฯลฯ

ฉันเขียนคลาสพื้นฐาน (RichEnum <>) เพื่อจัดการกับงานที่หนักหน่วงที่สุดซึ่งจะช่วยลดการประกาศ enums ข้างต้นลงไปที่:

public sealed class AccountStatus : RichEnum<byte, AccountStatus>
{
    public static readonly AccountStatus Open = new AccountStatus(1);
    public static readonly AccountStatus Closed = new AccountStatus(2);

    private AccountStatus(byte value) : base (value)
    {
    }

    public static implicit operator AccountStatus(byte value)
    {
        return Convert(value);
    }
}

คลาสพื้นฐาน (RichEnum) แสดงอยู่ด้านล่าง

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Resources;

namespace Ethica
{
    using Reflection;
    using Text;

    [DebuggerDisplay("{Value} ({Name})")]
    public abstract class RichEnum<TValue, TDerived>
                : IEquatable<TDerived>,
                  IComparable<TDerived>,
                  IComparable, IComparer<TDerived>
        where TValue : struct , IComparable<TValue>, IEquatable<TValue>
        where TDerived : RichEnum<TValue, TDerived>
    {
        #region Backing Fields

        /// <summary>
        /// The value of the enum item
        /// </summary>
        public readonly TValue Value;

        /// <summary>
        /// The public field name, determined from reflection
        /// </summary>
        private string _name;

        /// <summary>
        /// The DescriptionAttribute, if any, linked to the declaring field
        /// </summary>
        private DescriptionAttribute _descriptionAttribute;

        /// <summary>
        /// Reverse lookup to convert values back to local instances
        /// </summary>
        private static SortedList<TValue, TDerived> _values;

        private static bool _isInitialized;


        #endregion

        #region Constructors

        protected RichEnum(TValue value)
        {
            if (_values == null)
                _values = new SortedList<TValue, TDerived>();
            this.Value = value;
            _values.Add(value, (TDerived)this);
        }

        #endregion

        #region Properties

        public string Name
        {
            get
            {
                CheckInitialized();
                return _name;
            }
        }

        public string Description
        {
            get
            {
                CheckInitialized();

                if (_descriptionAttribute != null)
                    return _descriptionAttribute.Description;

                return _name;
            }
        }

        #endregion

        #region Initialization

        private static void CheckInitialized()
        {
            if (!_isInitialized)
            {
                ResourceManager _resources = new ResourceManager(typeof(TDerived).Name, typeof(TDerived).Assembly);

                var fields = typeof(TDerived)
                                .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                                .Where(t => t.FieldType == typeof(TDerived));

                foreach (var field in fields)
                {

                    TDerived instance = (TDerived)field.GetValue(null);
                    instance._name = field.Name;
                    instance._descriptionAttribute = field.GetAttribute<DescriptionAttribute>();

                    var displayName = field.Name.ToPhrase();
                }
                _isInitialized = true;
            }
        }

        #endregion

        #region Conversion and Equality

        public static TDerived Convert(TValue value)
        {
            return _values[value];
        }

        public static bool TryConvert(TValue value, out TDerived result)
        {
            return _values.TryGetValue(value, out result);
        }

        public static implicit operator TValue(RichEnum<TValue, TDerived> value)
        {
            return value.Value;
        }

        public static implicit operator RichEnum<TValue, TDerived>(TValue value)
        {
            return _values[value];
        }

        public static implicit operator TDerived(RichEnum<TValue, TDerived> value)
        {
            return value;
        }

        public override string ToString()
        {
            return _name;
        }

        #endregion

        #region IEquatable<TDerived> Members

        public override bool Equals(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.Equals((TValue)obj);

                if (obj is TDerived)
                    return Value.Equals(((TDerived)obj).Value);
            }
            return false;
        }

        bool IEquatable<TDerived>.Equals(TDerived other)
        {
            return Value.Equals(other.Value);
        }


        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        #endregion

        #region IComparable Members

        int IComparable<TDerived>.CompareTo(TDerived other)
        {
            return Value.CompareTo(other.Value);
        }

        int IComparable.CompareTo(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.CompareTo((TValue)obj);

                if (obj is TDerived)
                    return Value.CompareTo(((TDerived)obj).Value);
            }
            return -1;
        }

        int IComparer<TDerived>.Compare(TDerived x, TDerived y)
        {
            return (x == null) ? -1 :
                   (y == null) ? 1 :
                    x.Value.CompareTo(y.Value);
        }

        #endregion

        public static IEnumerable<TDerived> Values
        {
            get
            {
                return _values.Values;
            }
        }

        public static TDerived Parse(string name)
        {
            foreach (TDerived value in _values.Values)
                if (0 == string.Compare(value.Name, name, true) || 0 == string.Compare(value.DisplayName, name, true))
                    return value;

            return null;
        }
    }
}

แก้ไขข้อผิดพลาด offguard เล็กน้อยในโพสต์ :-) เป็นตัวดำเนินการโดยนัยแบบคงที่สาธารณะ AccountStatus (ค่าไบต์) {return Convert (value); } ไม่ส่งคืนการแปลง (ไบต์);
Mehdi LAMRANI

ฉันทำคอมไพล์ชั้นเบสนี้ คุณทราบไหมถ้าฉันแก้ไขการเปลี่ยนแปลงนี้
sehe

64
วิธีแก้ปัญหานี้อาจ 'ถูกต้อง' เป็นแบบฝึกหัดหรือทดสอบทักษะการเขียนโปรแกรมของใครบางคน แต่โปรดอย่าทำเช่นนี้ในชีวิตจริง ไม่เพียง แต่มันมากเกินไปมันไม่ก่อให้เกิดประโยชน์ไม่สามารถรักษาได้และน่าเกลียดเหมือนนรก คุณไม่จำเป็นต้องใช้ enum เพียงเพื่อประโยชน์ของมัน คุณใส่การแคสต์ที่ชัดเจนหรือเพียงแค่เขียนคลาสแบบคงที่ด้วย const ints
กับดัก

3
มันไม่ได้นำ Java enum มาใช้ใหม่โดยทั่วไปหรือไม่?
Agent_L

2
ปัญหาสำคัญอย่างหนึ่งคือคุณไม่สามารถใช้ค่าคงที่แบบอ่านอย่างเดียวแบบคงที่ในคำสั่ง switch
Ian Goldby

34

คุณไม่สามารถทำการแปลงโดยนัย (ยกเว้นศูนย์) และคุณไม่สามารถเขียนวิธีการอินสแตนซ์ของคุณเองได้อย่างไรก็ตามคุณสามารถเขียนวิธีการขยายของคุณเองได้:

public enum MyEnum { A, B, C }
public static class MyEnumExt
{
    public static int Value(this MyEnum foo) { return (int)foo; }
    static void Main()
    {
        MyEnum val = MyEnum.A;
        int i = val.Value();
    }
}

สิ่งนี้ไม่ได้ให้อะไรคุณมากมายนัก (เทียบกับการแคสต์ที่ชัดเจน)

ครั้งสำคัญครั้งหนึ่งที่ฉันเคยเห็นผู้คนต้องการสิ่งนี้คือการ[Flags]จัดการกับยาสามัญ - นั่นคือbool IsFlagSet<T>(T value, T flag);วิธีการ น่าเสียดายที่ C # 3.0 ไม่รองรับตัวดำเนินการเกี่ยวกับ generics แต่คุณสามารถหลีกเลี่ยงสิ่งนี้ได้โดยใช้สิ่งต่างๆเช่นนี้ซึ่งทำให้ตัวดำเนินการพร้อมใช้งานกับ generics ได้อย่างเต็มที่


ใช่นั่นเป็นหนึ่งในสิ่งที่ฉันต้องการมากที่สุดสำหรับ C # 4: stackoverflow.com/questions/138367/…และstackoverflow.com/questions/7244
Keith

@Keith - ทำได้ดีมาก; - p การสนับสนุนแบบไดนามิก / ตัวดำเนินการไม่ได้ทำให้เป็น CTP แต่ฉันมีอุปกรณ์ทดสอบพร้อมที่จะม้วนเพื่อเปรียบเทียบทั้งสองวิธีสำหรับตัวดำเนินการที่มีไดนามิก ( vs generics / Expression) เมื่อไปถึงที่นั่น
Marc Gravell

@Keith - คุณอาจต้องการให้คลาส Operator ใน MiscUtil หมุนวน ฉันค่อนข้างมั่นใจว่ามันจะทำสิ่งที่คุณต้องการได้มากที่สุด
Marc Gravell

22
struct PseudoEnum
{
    public const int 
              INPT = 0,
              CTXT = 1,
              OUTP = 2;
};

// ...

var arr = new String[3];

arr[PseudoEnum.CTXT] = "can";
arr[PseudoEnum.INPT] = "use";
arr[PseudoEnum.CTXT] = "as";
arr[PseudoEnum.CTXT] = "array";
arr[PseudoEnum.OUTP] = "index";

แต่ทำไมต้องมีโครงสร้าง?
Konrad

1
ไม่มีเหตุผลจริงๆ คุณสามารถใช้static classฉันคิดว่า ไม่มีข้อได้เปรียบในการโต้เถียงสำหรับทั้งสองกรณีในILรหัสสุดท้าย
Glenn Slayden

18

ฉันปรับคลาสพื้นฐาน RichEnum ที่ยอดเยี่ยมของ Mark

แก้ไข

  1. ปัญหาการคอมไพล์จำนวนมากเนื่องจากบิตที่หายไปจากไลบรารีของเขา (โดยเฉพาะอย่างยิ่ง: ชื่อที่แสดงที่ขึ้นกับทรัพยากรไม่ได้ถูกลบออกอย่างสมบูรณ์ตอนนี้)
  2. การเริ่มต้นไม่สมบูรณ์แบบ: หากสิ่งแรกที่คุณทำคือเข้าถึงคุณสมบัติ static .Values ​​จากคลาสพื้นฐานคุณจะได้รับ NPE แก้ไขสิ่งนี้โดยการบังคับให้คลาสพื้นฐานอยากรู้อยากเห็น - วนซ้ำ ( CRTP ) บังคับให้โครงสร้างคงที่ของ TDerived ทันเวลาระหว่าง CheckInitialized
  3. ในที่สุดก็ย้ายตรรกะ CheckInitialized ไปเป็นตัวสร้างแบบคงที่ (เพื่อหลีกเลี่ยงการถูกลงโทษในการตรวจสอบทุกครั้งสภาพการแข่งขันในการเริ่มต้นแบบมัลติเธรดบางทีนี่อาจเป็นไปไม่ได้ที่จะแก้ไขโดยสัญลักษณ์แสดงหัวข้อย่อยของฉัน?)

ขอชื่นชมสำหรับความคิดที่ยอดเยี่ยม + การใช้งานนี่คือสิ่งที่คุณทุกคน:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Resources;

namespace NMatrix
{

    [DebuggerDisplay("{Value} ({Name})")]
    public abstract class RichEnum<TValue, TDerived>
                : IEquatable<TDerived>,
                  IComparable<TDerived>,
                  IComparable, IComparer<TDerived>
        where TValue : struct, IComparable<TValue>, IEquatable<TValue>
        where TDerived : RichEnum<TValue, TDerived>
    {
        #region Backing Fields

        /// <summary>
        /// The value of the enum item
        /// </summary>
        public readonly TValue Value;

        /// <summary>
        /// The public field name, determined from reflection
        /// </summary>
        private string _name;

        /// <summary>
        /// The DescriptionAttribute, if any, linked to the declaring field
        /// </summary>
        private DescriptionAttribute _descriptionAttribute;

        /// <summary>
        /// Reverse lookup to convert values back to local instances
        /// </summary>
        private static readonly SortedList<TValue, TDerived> _values = new SortedList<TValue, TDerived>();

        #endregion

        #region Constructors

        protected RichEnum(TValue value)
        {
            this.Value = value;
            _values.Add(value, (TDerived)this);
        }

        #endregion

        #region Properties

        public string Name
        {
            get
            {
                return _name;
            }
        }

        public string Description
        {
            get
            {
                if (_descriptionAttribute != null)
                    return _descriptionAttribute.Description;

                return _name;
            }
        }

        #endregion

        #region Initialization

        static RichEnum()
        {
            var fields = typeof(TDerived)
                .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                .Where(t => t.FieldType == typeof(TDerived));

            foreach (var field in fields)
            {
                /*var dummy =*/ field.GetValue(null); // forces static initializer to run for TDerived

                TDerived instance = (TDerived)field.GetValue(null);
                instance._name = field.Name;
                                    instance._descriptionAttribute = field.GetCustomAttributes(true).OfType<DescriptionAttribute>().FirstOrDefault();
            }
        }

        #endregion

        #region Conversion and Equality

        public static TDerived Convert(TValue value)
        {
            return _values[value];
        }

        public static bool TryConvert(TValue value, out TDerived result)
        {
            return _values.TryGetValue(value, out result);
        }

        public static implicit operator TValue(RichEnum<TValue, TDerived> value)
        {
            return value.Value;
        }

        public static implicit operator RichEnum<TValue, TDerived>(TValue value)
        {
            return _values[value];
        }

        public static implicit operator TDerived(RichEnum<TValue, TDerived> value)
        {
            return value;
        }

        public override string ToString()
        {
            return _name;
        }

        #endregion

        #region IEquatable<TDerived> Members

        public override bool Equals(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.Equals((TValue)obj);

                if (obj is TDerived)
                    return Value.Equals(((TDerived)obj).Value);
            }
            return false;
        }

        bool IEquatable<TDerived>.Equals(TDerived other)
        {
            return Value.Equals(other.Value);
        }


        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        #endregion

        #region IComparable Members

        int IComparable<TDerived>.CompareTo(TDerived other)
        {
            return Value.CompareTo(other.Value);
        }

        int IComparable.CompareTo(object obj)
        {
            if (obj != null)
            {
                if (obj is TValue)
                    return Value.CompareTo((TValue)obj);

                if (obj is TDerived)
                    return Value.CompareTo(((TDerived)obj).Value);
            }
            return -1;
        }

        int IComparer<TDerived>.Compare(TDerived x, TDerived y)
        {
            return (x == null) ? -1 :
                   (y == null) ? 1 :
                    x.Value.CompareTo(y.Value);
        }

        #endregion

        public static IEnumerable<TDerived> Values
        {
            get
            {
                return _values.Values;
            }
        }

        public static TDerived Parse(string name)
        {
            foreach (TDerived value in Values)
                if (0 == string.Compare(value.Name, name, true))
                    return value;

            return null;
        }
    }
}

ตัวอย่างการใช้งานที่ใช้กับระบบโมโน:

using System.ComponentModel;
using System;

namespace NMatrix
{    
    public sealed class MyEnum : RichEnum<int, MyEnum>
    {
        [Description("aap")]  public static readonly MyEnum my_aap   = new MyEnum(63000);
        [Description("noot")] public static readonly MyEnum my_noot  = new MyEnum(63001);
        [Description("mies")] public static readonly MyEnum my_mies  = new MyEnum(63002);

        private MyEnum(int value) : base (value) { } 
        public static implicit operator MyEnum(int value) { return Convert(value); }
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            foreach (var enumvalue in MyEnum.Values)
                Console.WriteLine("MyEnum {0}: {1} ({2})", (int) enumvalue, enumvalue, enumvalue.Description);
        }
    }
}

การผลิตเอาต์พุต

[mono] ~/custom/demo @ gmcs test.cs richenum.cs && ./test.exe 
MyEnum 63000: my_aap (aap)
MyEnum 63001: my_noot (noot)
MyEnum 63002: my_mies (mies)

หมายเหตุ: mono 2.6.7 ต้องการการร่ายที่ชัดเจนเป็นพิเศษซึ่งไม่จำเป็นเมื่อใช้ mono 2.8.2 ...


การใช้. Single () เพื่อรับแอตทริบิวต์ description ไม่ใช่ความคิดที่ดี หากไม่มีแอตทริบิวต์ Single () จะแสดงข้อยกเว้น SingleOrDefault () จะไม่
kerem

@kerem จุดดีฉันอัปเดตแล้ว (โดยใช้FirstOrDefaultเพื่อหลีกเลี่ยงการสมมติว่ามีแอตทริบิวต์เดียวเท่านั้น) ไม่ว่าสิ่งนั้นจะเป็น 'ความคิดที่ดี' หรือไม่ (หรือไม่ดีสำหรับเรื่องนั้น) นั้นขึ้นอยู่กับบริบท
เห็น

1
รักสิ่งนี้ แต่ฉันพบปัญหา: บน Windows 7 / .NET 4.5 บรรทัดนี้TDerived instance = (TDerived)field.GetValue(null);ส่งผลให้instanceเป็นnull. ดูเหมือนว่ารันไทม์ Mono ต้องมีลำดับการเริ่มต้นประเภทต่างจาก. NET ที่อนุญาตให้ทำงานได้ งง! ฉันต้องย้ายรหัสนั้นไปยังวิธีการแบบคงที่แทนและเรียกใช้จากประเภท initializer ในคลาสย่อย
agentnega

@agentnega ขอบคุณสำหรับการเพิ่มนั้น มันอาจช่วยใครบางคนได้
sehe

@agentnega ฉันประสบปัญหาเดียวกันใน. net 4.5.1 ดูเหมือนว่าจะ "ละเมิด" ข้อกำหนดC # b / c ซึ่งจะไม่เริ่มต้นค่าก่อนการใช้งานครั้งแรก - อย่างน้อยก็ไม่ใช่เมื่อใช้การสะท้อน ฉันได้ใช้วิธีแก้ปัญหาที่ไม่ต้องการให้คลาสย่อย ('TDerived') เข้ามาเกี่ยวข้อง @ ฉันควรแก้ไขคำตอบของคุณและเพิ่มวิธีแก้ปัญหาในคำตอบของคุณหรือฉันควรโพสต์คำตอบใหม่
BatteryBackupUn ใน

5

คุณไม่สามารถประกาศการแปลงโดยนัยในประเภท enum ได้เนื่องจากไม่สามารถกำหนดวิธีการได้ คีย์เวิร์ดโดยนัย C # รวบรวมไว้ในเมธอดที่ขึ้นต้นด้วย 'op_' และจะใช้ไม่ได้ในกรณีนี้


4

คุณอาจทำได้ แต่ไม่ใช่สำหรับ enum (คุณไม่สามารถเพิ่มวิธีการได้) คุณสามารถเพิ่มการแปลงโดยนัยให้กับชั้นเรียนของคุณเองเพื่อให้สามารถแปลง enum ได้

public class MyClass {

    public static implicit operator MyClass ( MyEnum input ) {
        //...
    }
}

MyClass m = MyEnum.One;

คำถามจะทำไม?

โดยทั่วไป. Net จะหลีกเลี่ยง (และคุณก็ควรเช่นกัน) การแปลงโดยปริยายใด ๆ ที่ข้อมูลอาจสูญหายได้


3

หากคุณกำหนดฐานของ enum เป็นความยาวคุณสามารถทำการแปลงอย่างชัดเจนได้ ฉันไม่รู้ว่าคุณสามารถใช้การแปลงโดยนัยได้หรือไม่เนื่องจาก enums ไม่สามารถกำหนดวิธีการได้

public enum MyEnum : long
{
    one = 1,
    two = 2,
}

MyEnum number = MyEnum.one;
long i = (long)number;

นอกจากนี้โปรดทราบด้วยว่าการแจงนับที่ไม่ได้ระบุค่าเริ่มต้นจะเป็นค่าเริ่มต้นเป็นค่า 0 หรือรายการแรกดังนั้นในสถานการณ์ข้างต้นจึงควรกำหนดzero = 0ด้วยเช่นกัน


5
คุณไม่ต้องการที่: longนี่ การแปลงอย่างชัดเจนจะทำงานได้ดีหากไม่มีมัน การแปลงโดยนัยทางกฎหมายเพียงอย่างเดียวคือศูนย์
Marc Gravell

3
ไม่มี enum เริ่มต้นคือ Int32
Marc Gravell

1
ดู: enum Foo {A, B, C} Console.WriteLine (Enum.GetUnderlyingType (typeof (Foo)));
Marc Gravell

14
เหตุใดจึงถูกทำเครื่องหมายว่าเป็นคำตอบและมีคะแนนมาก เรื่องนี้ไม่เกี่ยวข้องกับคำถาม OP !!! เขากำลังพูดถึง IMPLICIT Conversion ... มูลค่าเพิ่มคือศูนย์
Mehdi LAMRANI

3
คำถามนี้บอกเป็นนัยว่าเข้าใจการแคสต์อย่างโจ่งแจ้งแล้วคำถามจะเทียบเท่ากับการถามว่า "ฉันจะหลีกเลี่ยงการแคสต์อย่างโจ่งแจ้งได้อย่างไร" ซึ่งโพสต์นี้ไม่สามารถนำไปใช้ได้
Kit10

2

enums ส่วนใหญ่ไร้ประโยชน์สำหรับฉันเพราะเหตุนี้ OP

ฉันจบลงด้วยการทำภาพที่เกี่ยวข้องตลอดเวลา:

วิธีง่ายๆ

ปัญหาตัวอย่างคลาสสิกคือชุด VirtualKey สำหรับตรวจจับการกดแป้นพิมพ์

enum VKeys : ushort
{
a = 1,
b = 2,
c = 3
}
// the goal is to index the array using predefined constants
int[] array = new int[500];
var x = array[VKeys.VK_LSHIFT]; 

ปัญหาที่นี่คือคุณไม่สามารถจัดทำดัชนีอาร์เรย์ด้วย enum ได้เนื่องจากไม่สามารถแปลง enum เป็น ushort โดยปริยายได้ (แม้ว่าเราจะใช้ enum ใน ushort)

ในบริบทเฉพาะนี้ enums จะล้าสมัยโดยโครงสร้างข้อมูลต่อไปนี้ . . .

public static class VKeys
{
public const ushort
a = 1,
b = 2, 
c = 3;
}

1

ฉันได้แก้ไขปัญหาเกี่ยวกับคำตอบของ seheเมื่อเรียกใช้รหัสบน MS .net (ไม่ใช่ Mono) สำหรับฉันโดยเฉพาะปัญหาเกิดขึ้นบน. net 4.5.1 แต่ดูเหมือนว่าเวอร์ชันอื่น ๆ จะได้รับผลกระทบเช่นกัน

ปัญหา

การเข้าถึงpublic static TDervied MyEnumValueโดยการสะท้อนกลับ (ผ่านFieldInfo.GetValue(null)ไม่ได้เริ่มต้นฟิลด์ดังกล่าว

วิธีแก้ปัญหา

แทนการกำหนดชื่อTDerivedอินสแตนซ์เมื่อ initializer คงที่ของนี้จะทำอย่างเฉื่อยชาในการเข้าถึงแรกของRichEnum<TValue, TDerived> TDerived.Nameรหัส:

public abstract class RichEnum<TValue, TDerived> : EquatableBase<TDerived>
    where TValue : struct, IComparable<TValue>, IEquatable<TValue>
    where TDerived : RichEnum<TValue, TDerived>
{
    // Enforcing that the field Name (´SomeEnum.SomeEnumValue´) is the same as its 
    // instances ´SomeEnum.Name´ is done by the static initializer of this class.
    // Explanation of initialization sequence:
    // 1. the static initializer of ´RichEnum<TValue, TDerived>´ reflects TDervied and 
    //    creates a list of all ´public static TDervied´ fields:
    //   ´EnumInstanceToNameMapping´
    // 2. the static initializer of ´TDerive´d assigns values to these fields
    // 3. The user is now able to access the values of a field.
    //    Upon first access of ´TDervied.Name´ we search the list 
    //    ´EnumInstanceToNameMapping´ (created at step 1) for the field that holds
    //    ´this´ instance of ´TDerived´.
    //    We then get the Name for ´this´ from the FieldInfo
    private static readonly IReadOnlyCollection<EnumInstanceReflectionInfo> 
                            EnumInstanceToNameMapping = 
        typeof(TDerived)
            .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
            .Where(t => t.FieldType == typeof(TDerived))
            .Select(fieldInfo => new EnumInstanceReflectionInfo(fieldInfo))
            .ToList();

    private static readonly SortedList<TValue, TDerived> Values =
        new SortedList<TValue, TDerived>();

    public readonly TValue Value;

    private readonly Lazy<string> _name;

    protected RichEnum(TValue value)
    {
        Value = value;

        // SortedList doesn't allow duplicates so we don't need to do
        // duplicate checking ourselves
        Values.Add(value, (TDerived)this);

        _name = new Lazy<string>(
                    () => EnumInstanceToNameMapping
                         .First(x => ReferenceEquals(this, x.Instance))
                         .Name);
    }

    public string Name
    {
        get { return _name.Value; }
    }

    public static implicit operator TValue(RichEnum<TValue, TDerived> richEnum)
    {
        return richEnum.Value;
    }

    public static TDerived Convert(TValue value)
    {
        return Values[value];
    }

    protected override bool Equals(TDerived other)
    {
        return Value.Equals(other.Value);
    }

    protected override int ComputeHashCode()
    {
        return Value.GetHashCode();
    }

    private class EnumInstanceReflectionInfo
    {
        private readonly FieldInfo _field;
        private readonly Lazy<TDerived> _instance;

        public EnumInstanceReflectionInfo(FieldInfo field)
        {
            _field = field;
            _instance = new Lazy<TDerived>(() => (TDerived)field.GetValue(null));
        }

        public TDerived Instance
        {
            get { return _instance.Value; }
        }

        public string Name { get { return _field.Name; } }
    }
}

ซึ่ง - ในกรณีของฉัน - ขึ้นอยู่กับEquatableBase<T>:

public abstract class EquatableBase<T>
    where T : class 
{
    public override bool Equals(object obj)
    {
        if (this == obj)
        {
            return true;
        }

        T other = obj as T;
        if (other == null)
        {
            return false;
        }

        return Equals(other);
    }

    protected abstract bool Equals(T other);

    public override int GetHashCode()
    {
        unchecked
        {
            return ComputeHashCode();
        }
    }

    protected abstract int ComputeHashCode();
}

บันทึก

โค้ดข้างต้นไม่ได้รวมคุณสมบัติทั้งหมดของคำตอบเดิมของMark !

ขอบคุณ

ขอขอบคุณที่มาร์คสำหรับการให้บริการของเขาRichEnumดำเนินการและขอขอบคุณที่seheสำหรับการให้บริการการปรับปรุงบางอย่าง!


1

ฉันพบวิธีแก้ปัญหาที่ง่ายยิ่งขึ้นจากที่นี่/codereview/7566/enum-vs-int-wrapper-structฉันวางโค้ดด้านล่างจากลิงค์นั้นในกรณีที่ไม่สามารถใช้งานได้ในอนาคต

struct Day
{
    readonly int day;

    public static readonly Day Monday = 0;
    public static readonly Day Tuesday = 1;
    public static readonly Day Wednesday = 2;
    public static readonly Day Thursday = 3;
    public static readonly Day Friday = 4;
    public static readonly Day Saturday = 5;
    public static readonly Day Sunday = 6;

    private Day(int day)
    {
        this.day = day;
    }

    public static implicit operator int(Day value)
    {
        return value.day;
    }

    public static implicit operator Day(int value)
    {
        return new Day(value);
    }
}

1

ฉันสร้างอรรถประโยชน์นี้จะช่วยให้ฉันแปลงEnumเพื่อPrimitiveEnumและPrimitiveEnumbyte, sbyte, short, ushort, int, uint, long, or ulongไป

ดังนั้นในทางเทคนิคแล้วสิ่งนี้จะแปลง enum เป็นค่าดั้งเดิม

public enum MyEnum
{
    one = 1, two = 2
}

PrimitiveEnum number = MyEnum.one;
long i = number;

ดูการกระทำที่https://github.com/McKabue/McKabue.Extentions.Utility/blob/master/src/McKabue.Extentions.Utility/Enums/PrimitiveEnum.cs

using System;

namespace McKabue.Extentions.Utility.Enums
{
    /// <summary>
    /// <see href="https://stackoverflow.com/q/261663/3563013">
    /// Can we define implicit conversions of enums in c#?
    /// </see>
    /// </summary>
    public struct PrimitiveEnum
    {
        private Enum _enum;

        public PrimitiveEnum(Enum _enum)
        {
            this._enum = _enum;
        }

        public Enum Enum => _enum;


        public static implicit operator PrimitiveEnum(Enum _enum)
        {
            return new PrimitiveEnum(_enum);
        }

        public static implicit operator Enum(PrimitiveEnum primitiveEnum)
        {
            return primitiveEnum.Enum;
        }

        public static implicit operator byte(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToByte(primitiveEnum.Enum);
        }

        public static implicit operator sbyte(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToSByte(primitiveEnum.Enum);
        }

        public static implicit operator short(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt16(primitiveEnum.Enum);
        }

        public static implicit operator ushort(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt16(primitiveEnum.Enum);
        }

        public static implicit operator int(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt32(primitiveEnum.Enum);
        }

        public static implicit operator uint(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt32(primitiveEnum.Enum);
        }

        public static implicit operator long(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt64(primitiveEnum.Enum);
        }

        public static implicit operator ulong(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt64(primitiveEnum.Enum);
        }
    }
}

+1 ฉันมีเฟรมเวิร์กของเกมที่มีหลายสิ่งที่ระบุโดยuints ซึ่งเกมนั้นมักจะสร้างมาenumเพื่อ แต่เฟรมเวิร์กนั้นไม่รู้อะไรเลย เมื่อต้อง(uint)เรียกเฟรมเวิร์กเป็นความเจ็บปวด ความคิดของคุณย้อนกลับทำงานได้อย่างสมบูรณ์ แทนที่จะstructจัดเก็บEnumฉันมีstruct IdNumberที่เก็บuintแต่โดยปริยายแปลงจากEnumเกมที่ใช้ แทนที่จะพิมพ์พารามิเตอร์ของเฟรมเวิร์กเป็นuintฉันสามารถพิมพ์ได้IdNumberและเฟรมเวิร์กสามารถส่งผ่านสิ่งเหล่านี้ไปได้ภายในอย่างมีประสิทธิภาพแม้กระทั่งการดำเนินการที่สำคัญ
Kevin

-2

การแนะนำการแปลงโดยนัยสำหรับประเภท enum จะทำลายความปลอดภัยของประเภทดังนั้นฉันไม่แนะนำให้ทำเช่นนั้น ทำไมคุณถึงอยากทำเช่นนั้น? กรณีการใช้งานเดียวที่ฉันเคยเห็นคือเมื่อคุณต้องการใส่ค่า enum ลงในโครงสร้างที่มีเค้าโครงที่กำหนดไว้ล่วงหน้า แต่ถึงอย่างนั้นคุณสามารถใช้ enum type ในโครงสร้างและบอก Marshaller ว่าเขาควรทำอย่างไรกับสิ่งนี้


ฉันมีประโยชน์สำหรับการแปลง enums โดยปริยาย การใช้ SPMetal เพื่อสร้าง LINQ ไปยังคลาส SharePoint ในหลายไซต์ของไซต์คอลเลกชันเดียวกัน รายการของฉันบางรายการอยู่ในไซต์ย่อยเดียวส่วนรายการอื่น ๆ ในไซต์ย่อยอื่น เนื่องจากวิธีที่ SPMetal สร้างรหัสคอลัมน์ของไซต์ที่ใช้ในหลายรายการของคอลเลกชันอาจถูกกำหนดในหลายเนมสเปซ อย่างไรก็ตามฉันจำเป็นต้องแปลงระหว่าง enum ฟิลด์ตัวเลือกในเนมสเปซหนึ่งกับ enum เดียวกันในเนมสเปซอื่น การแปลงโดยนัยจะมีประโยชน์มาก
Zarepheth
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.