1. 程式人生 > >在ASP.Net Core 中使用列舉類而不是列舉

在ASP.Net Core 中使用列舉類而不是列舉

前言:我相信大家在編寫程式碼時經常會遇到各種狀態值,而且為了避免硬編碼和程式碼中出現魔法數,通常我們都會定義一個列舉,來表示各種狀態值,直到我看到Java中這樣使用列舉,我再想C# 中可不可以這樣寫,今天就分享一下我的感悟。

 

一、通常我們是這樣使用列舉的

(1)switch中使用列舉

 public enum  EmployeeType
    {
        Manager,
        Servant,
        AssistantToTheRegionalManager
    }
public class Employee
    {
        public EmployeeType Type { get; set; }
        public decimal Bonus { get; set; }
    }
static void ProcessBonus(Employee employee)
        {
            switch (employee.Type)
            {
                case EmployeeType.Manager:
                    employee.Bonus = 1000m;
                    break;
                case EmployeeType.Servant:
                    employee.Bonus = 0.01m;
                    break;
                case EmployeeType.AssistantToTheRegionalManager:
                    employee.Bonus = 1.0m;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

在沒有進某唐時我也是這樣的寫的,程式碼很爛,違法了開閉原則,擴充套件性極差。在程式碼規範中是不允許出現這樣的寫法的。對於上面的寫法可以使用設計模式來重構。後面會繼續更新設計模式的文章。

(2)型別轉換

EnumTricks.IsVolumeHigh((Volume)27);
EnumTricks.High((int)Medium);

 

二、列舉的不好之處

關於列舉的MSDN文件說了什麼:

“The enum keyword is used to declare an enumeration, a distinct type that consists of a set of named constants called the enumerator list. Every enumeration type has an underlying type, which can be any integral type except char. The default underlying type of the enumeration elements is int. By default, the first enumerator has the value 0, and the value of each successive enumerator is increased by 1.

(1)沒有型別安全

列舉是簡單的值型別,可以提供對無效值的保護,並且不會出現任何行為。他們是有用的,因為他們是魔法數字的改進,但就是這樣。如果要約束型別可能的值,列舉不一定能幫助您,因為仍然可以提供無效型別。例如,此列舉有三個值,預設情況下將具有int型別。值範圍為1到3。

 public enum Volume
    {
        Low = 1,
        Medium,
        High
    }
public static class EnumTricks
    {
        public static bool IsVolumeHigh(Volume volume)
        {
            var result = false;

            switch (volume)
            {
                case Volume.Low:
                    Console.WriteLine("Volume is low.");
                    break;

                case Volume.Medium:
                    Console.WriteLine("Volume is medium.");
                    break;

                case Volume.High:
                    Console.WriteLine("Volume is high.");
                    result = true;
                    break;
            }

            return result;
        }
    }
 static void Main(string[] args)
        {
            EnumTricks.IsVolumeHigh((Volume)27);



            Console.ReadKey();
        }
public static class EnumTricks
    {
        public static bool IsVolumeHigh(Volume volume)
        {
            var result = false;

            switch (volume)
            {
                case Volume.Low:
                    Console.WriteLine("Volume is low.");
                    break;

                case Volume.Medium:
                    Console.WriteLine("Volume is medium.");
                    break;

                case Volume.High:
                    Console.WriteLine("Volume is high.");
                    result = true;
                    break;
            }

            return result;
        }

        public static int EnumToInt(Volume volume)
        {
            return (int)volume;
        }

        public static Volume IntToEnum(int intValue)
        {
            return (Volume)intValue;
        }

        public static Volume StringToEnum(string stringValue)
        {
            return (Volume)Enum.Parse(typeof(Volume), stringValue);
        }

        public static int StringToInt(string stringValue)
        {
            var volume = StringToEnum(stringValue);
            return EnumToInt(volume);
        }
        public static string EnumToString(Volume volume)
        {
            return volume.ToString();
        }
    }
View Code

 

這應該失敗,至少在執行時。它沒有。這真的很奇怪......在編譯期間或執行期間都不會檢測到錯誤的呼叫。你會覺得自己處於一個虛假的安全狀態。如果,我們把傳進去的列舉轉換為string時,來看看這兩種情況有什麼不同:

 我不知道大家平時在使用列舉的時候,是否有意識檢查傳入的是否是有效的值。可以使用Enum.IsDefined()來檢查int值是否是一個有效的值

解決方案:如果int值在列舉值的定義範圍內,則使用Enum.IsDefined()查詢。如果在範圍內,則返回True,否則返回False。

(2)轉化

您是否嘗試過將enum轉換為int,int轉換為enum,string轉換為enum,將字串轉換為enum的int值?如下程式碼:

 public static class EnumTricks
    {
        public static bool IsVolumeHigh(Volume volume)
        {
            var result = false;

            switch (volume)
            {
                case Volume.Low:
                    Console.WriteLine("Volume is low.");
                    break;

                case Volume.Medium:
                    Console.WriteLine("Volume is medium.");
                    break;

                case Volume.High:
                    Console.WriteLine("Volume is high.");
                    result = true;
                    break;
            }

            return result;
        }

        public static int EnumToInt(Volume volume)
        {
            return (int)volume;
        }

        public static Volume IntToEnum(int intValue)
        {
            return (Volume)intValue;
        }

        public static Volume StringToEnum(string stringValue)
        {
            return (Volume)Enum.Parse(typeof(Volume), stringValue);
        }

        public static int StringToInt(string stringValue)
        {
            var volume = StringToEnum(stringValue);
            return EnumToInt(volume);
        }
    }

 是不是我們日常的程式碼中也有這樣的型別轉換程式碼,不是說不好,只是型別轉換也是有效能損失的,如果能換中方式可以同樣實現而且還避免以上問題豈不是更好,這樣我們的程式碼也更好維護和擴充套件,下面我們通過使用列舉類的方式來解決這個問題。

三、使用列舉類而不是列舉型別

public class Enumeration: IComparable
    {
        private readonly int _value;
        private readonly string _displayName;

        protected Enumeration()
        {
        }

        protected Enumeration(int value, string displayName)
        {
            _value = value;
            _displayName = displayName;
        }

        public int Value
        {
            get { return _value; }
        }

        public string DisplayName
        {
            get { return _displayName; }
        }

        public override string ToString()
        {
            return DisplayName;
        }

        public static IEnumerable<T> GetAll<T>() where T : Enumeration, new()
        {
            var type = typeof(T);
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

            foreach (var info in fields)
            {
                var instance = new T();
                var locatedValue = info.GetValue(instance) as T;

                if (locatedValue != null)
                {
                    yield return locatedValue;
                }
            }
        }

        public override bool Equals(object obj)
        {
            var otherValue = obj as Enumeration;

            if (otherValue == null)
            {
                return false;
            }

            var typeMatches = GetType().Equals(obj.GetType());
            var valueMatches = _value.Equals(otherValue.Value);

            return typeMatches && valueMatches;
        }

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

        public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
        {
            var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value);
            return absoluteDifference;
        }

        public static T FromValue<T>(int value) where T : Enumeration, new()
        {
            var matchingItem = parse<T, int>(value, "value", item => item.Value == value);
            return matchingItem;
        }

        public static T FromDisplayName<T>(string displayName) where T : Enumeration, new()
        {
            var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName);
            return matchingItem;
        }

        private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
        {
            var matchingItem = GetAll<T>().FirstOrDefault(predicate);

            if (matchingItem == null)
            {
                var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
                throw new ApplicationException(message);
            }

            return matchingItem;
        }

        public int CompareTo(object other)
        {
            return Value.CompareTo(((Enumeration)other).Value);
        }
    }
View Code
public class Volume: Enumeration
    {
        private Volume() { throw new Exception(""); }
        private Volume(int value, string displayName): base(value, displayName) { }


        public static readonly Volume Low = new Volume(1, nameof(Low).ToLowerInvariant());
        public static readonly Volume Medium = new Volume(2, nameof(Medium).ToLowerInvariant());
        public static readonly Volume High = new Volume(3, nameof(High).ToLowerInvariant());


        public static IEnumerable<Volume> List() =>
            new[] { Low, Medium, High };

        public static Volume From(int value)
        {
            var state = List().SingleOrDefault(s => s.Value == value);

            if (state == null)
            {
                throw new Exception($"Possible values for Volume: {String.Join(",", List().Select(s => s.Value))}");
            }

            return state;
        }

        public static Volume FromName(string name)
        {
            var state = List()
                .SingleOrDefault(s => String.Equals(s.DisplayName, name, StringComparison.CurrentCultureIgnoreCase));

            if (state == null)
            {
                throw new Exception($"Possible values for Volume: {String.Join(",", List().Select(s => s.DisplayName))}");
            }

            return state;
        }
    }
static void Main(string[] args)
        {
            //EnumTricks.IsVolumeHigh((Volume)27);

            //var tmp = Enum.IsDefined(typeof(Volume), 3);
            //var str = EnumTricks.EnumToString((Volume)27);
            //var str2 = EnumTricks.EnumToString((Volume)3);


            //Console.WriteLine($"Volume 27:{str}");
            //Console.WriteLine($"Volume 3:{str2}");

            Console.WriteLine("------------------------------------------------------------");
            
            Console.WriteLine(Volume.High.Value);
            Console.WriteLine(Volume.High.DisplayName);

            var volume = Volume.From(2);
            var volume2 = Volume.FromName("high");
            var none = Volume.From(27);

            Console.ReadKey();
        }

 

四、應用

程式碼如下:

Error檔案下:

public interface ICommonError
    {
        int GetErrCode();
        string GetErrMsg();
        ICommonError SetErrMsg(string errMsg);
    }
public class Enumeration : IComparable
    {
        private readonly int _value;
        private readonly string _displayName;

        protected Enumeration()
        {
        }

        protected Enumeration(int value, string displayName)
        {
            _value = value;
            _displayName = displayName;
        }

        public int Value
        {
            get { return _value; }
        }

        public string DisplayName
        {
            get { return _displayName; }
        }

        public override string ToString()
        {
            return DisplayName;
        }

        public static IEnumerable<T> GetAll<T>() where T : Enumeration, new()
        {
            var type = typeof(T);
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

            foreach (var info in fields)
            {
                var instance = new T();
                var locatedValue = info.GetValue(instance) as T;

                if (locatedValue != null)
                {
                    yield return locatedValue;
                }
            }
        }

        public override bool Equals(object obj)
        {
            var otherValue = obj as Enumeration;

            if (otherValue == null)
            {
                return false;
            }

            var typeMatches = GetType().Equals(obj.GetType());
            var valueMatches = _value.Equals(otherValue.Value);

            return typeMatches && valueMatches;
        }

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

        public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
        {
            var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value);
            return absoluteDifference;
        }

        public static T FromValue<T>(int value) where T : Enumeration, new()
        {
            var matchingItem = parse<T, int>(value, "value", item => item.Value == value);
            return matchingItem;
        }

        public static T FromDisplayName<T>(string displayName) where T : Enumeration, new()
        {
            var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName);
            return matchingItem;
        }

        private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
        {
            var matchingItem = GetAll<T>().FirstOrDefault(predicate);

            if (matchingItem == null)
            {
                var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
                throw new ApplicationException(message);
            }

            return matchingItem;
        }

        public int CompareTo(object other)
        {
            return Value.CompareTo(((Enumeration)other).Value);
        }
    }
View Code
public class EmBusinessError : Enumeration, ICommonError
    {
        private int errCode;
        private String errMsg;

        public static readonly EmBusinessError parameterValidationError = new EmBusinessError(10001, "引數不合法");

        private EmBusinessError() { throw new Exception("私有建構函式不能呼叫"); }
        private EmBusinessError(int value, string displayName) : base(value, displayName) {

            this.errCode = value;
            this.errMsg = displayName;
        }
        
        public int GetErrCode()
        {
            return this.errCode;
        }

        public string GetErrMsg()
        {
            return this.errMsg;
        }

        public void SetErrCode(int errCode)
        {
            this.errCode = errCode;
        }

        public ICommonError SetErrMsg(string errMsg)
        {
            this.errMsg = errMsg;

            return this;
        }
    }
//包裝器業務異常類實現
    public class BusinessException : Exception, ICommonError
    {
        private ICommonError commonError;

        //直接接收EmBusinessError的傳參用於構造業務異常
        public BusinessException(ICommonError commonError):base()
        {
            this.commonError = commonError;
        }
        public BusinessException(ICommonError commonError, string errMsg):base()
        {
            this.commonError = commonError;
            this.commonError.SetErrMsg(errMsg);
        }
        public int GetErrCode()
        {
            return this.commonError.GetErrCode();
        }

        public string GetErrMsg()
        {
            return this.commonError.GetErrMsg();
        }

        public ICommonError SetErrMsg(string errMsg)
        {
            this.commonError.SetErrMsg(errMsg);

            return this;
        }
        public ICommonError GetCommonError()
        {
            return commonError;
        }
    }
View Code

 

異常中介軟體:

public class ExceptionHandlerMiddleWare
    {
        private readonly RequestDelegate next;
     
        /// <summary>
        /// 
        /// </summary>
        /// <param name="next"></param>
        public ExceptionHandlerMiddleWare(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }

        private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            if (exception == null) return;
            await WriteExceptionAsync(context, exception).ConfigureAwait(false);
        }

        private static async Task WriteExceptionAsync(HttpContext context, Exception exception)
        {
            var response = context.Response;
            response.ContentType = "application/json;charset=utf-8";
            var result = new CommonReturnType();
           

            if (exception is BusinessException)
            {
                var businessException = (BusinessException)exception;

                var errModel = new { errCode= businessException.GetErrCode(), errMsg= businessException.GetErrMsg() };

                result = CommonReturnType.Create(errModel, "fail");

                
            }
           

            await response.WriteAsync(JsonConvert.SerializeObject(new { data = result.GetData(), status = result.GetStatus() }) ).ConfigureAwait(false);
        }

    }

Response資料夾:

 public class CommonReturnType
    {
        //表明對應請求的返回處理結果 "success" 或 "fail"
        private string status;

        //若status=success,則data內返回前端需要的json資料
        //若status=fail,則data內使用通用的錯誤碼格式
        private object data;

        //定義一個通用的建立方法
        public static CommonReturnType Create(object result)
        {
            return CommonReturnType.Create(result, "success");
        }
        
        public static CommonReturnType Create(object result, string status)
        {
            CommonReturnType type = new CommonReturnType();
            type.SetStatus(status);
            type.SetData(result);

            return type;
        }

        public string GetStatus()
        {
            return status;
        }

        public void SetStatus(string status)
        {
            this.status = status;
        }

        public object GetData()
        {
            return data;
        }

        public void SetData(object data)
        {
            this.data = data;
        }
    }
View Code

 

最後推薦一個類庫,這是我在Nuget上發現的列舉類庫,地址:https://github.com/ardalis/SmartEnum

好了,先分享到這裡,希望對你有幫助和啟發。

參考資料:

(1)https://docs.microsoft.com/zh-cn/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types

(2)https://ardalis.com/enum-alternatives-in-c 

 

 

作者:郭崢

出處:http://www.cnblogs.com/runningsmallguo/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。