C#–特性詳解

一码事發表於2024-04-05

一、特性是什麼

1、特性定義

特性(Attribute)是用於在執行時傳遞程式中各種元素(比如類、方法、結構、列舉、元件等)的行為資訊的宣告性標籤。您可以透過使用特性向程式新增宣告性資訊。一個宣告性標籤是透過放置在它所應用的元素前面的方括號([ ])來描述的。

特性(Attribute)用於新增後設資料,如編譯器指令和註釋、描述、方法、類等其他資訊。.Net 框架提供了兩種型別的特性:預定義特性和自定義特性。

2、特性的語法

特性(Attribute)的名稱和值是在方括號內規定的,放置在它所應用的元素之前。positional_parameters 規定必需的資訊,name_parameter 規定可選的資訊。

1
2
[attribute(positional_parameters, name_parameter = value, ...)]
element

3、特性和註釋有什麼區別

特性很厲害,加了特性之後,就有很厲害的功能
[Obsolete]編譯時就有提示,影響了編譯器[Obsolete(“請不要使用這個了,請使用什麼來代替”, true)]甚至導致編譯報錯
[Serializable]物件就可以序列化,影響了程式執行


using System;

namespace MyAttribute
{
    /// <summary>
    /// 這裡是註釋,除了讓人看懂這裡寫的是什麼,對執行沒有任何影響
    /// </summary>
    ///[Obsolete("請不要使用這個了,請使用什麼來代替")]//對編譯都產生了影響,編譯出現警告
    ///[Obsolete("請不要使用這個了,請使用什麼來代替", true)]//對編譯都產生了影響,編譯報錯不透過
    [Serializable]//可以序列化和反序列化
    public class Student
    {        
        public int Id { get; set; }
        
        public string Name { get; set; }
        
        public void Study()
        {
            Console.WriteLine($"這裡是{this.Name}在學習");
        }
        
        public string Answer([Custom]string name)
        {
            return $"This is {name}";
        }
    }
}

特性無處不在:EF–MVC–WCF–WebService–UnitTest–IOC–AOP–SuperSocket

二、特性宣告和使用

1、什麼是特性

特性其實就是一個類,直接或間接繼承自Attribute


#region 程式集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll
// Decompiled with ICSharpCode.Decompiler 6.1.0.5902
#endregion

using System.Reflection;
using System.Runtime.InteropServices;

namespace System
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)]
    [ComVisible(true)]
    public sealed class SerializableAttribute : Attribute
    {
        internal static Attribute GetCustomAttribute(RuntimeType type)
        {
            if ((type.Attributes & TypeAttributes.Serializable) != TypeAttributes.Serializable)
            {
                return null;
            }

            return new SerializableAttribute();
        }

        internal static bool IsDefined(RuntimeType type)
        {
            return type.IsSerializable;
        }
    }
}
#if false // 反編譯日誌
快取中的 9 項
#endif

2、自定義一個特性


using System;

namespace MyAttribute
{    
    public class CustomAttribute : Attribute
    {
        
    }
}

約定俗成用Attribute結尾,標記時就可以省略掉;可以用中括號包裹,然後標記到元素,其實就是呼叫建構函式;


using System;

namespace MyAttribute
{
    [Custom]
    public class Student
    {
        [Custom]
        public int Id { get; set; }
        public string Name { get; set; }
        [Custom]
        public void Study()
        {
            Console.WriteLine($"這裡是{this.Name}跟著Gerry老師學習");
        }
    }
}

3、AttributeUsage特性

直接在一個元素上新增多個相同的特性,會報錯特性重複,需要在特性上面新增特性標記[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]這樣就可以給同一個元素新增多個相同的特性了


using System;

namespace MyAttribute
{
    [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {
            Console.WriteLine($"{this.GetType().Name} 無引數建構函式執行");
        }
        public CustomAttribute(int id)
        {
            Console.WriteLine($"{this.GetType().Name} int引數建構函式執行");
            this._Id = id;
        }
        public CustomAttribute(string name)
        {
            Console.WriteLine($"{this.GetType().Name} string引數建構函式執行");
            this._Name = name;
        }
    }
}

多個相同的特性情況展示


using System;

namespace MyAttribute
{    
    [Custom]
    [Custom()]    
    [Custom(10)]    
    public class Student
    {
        [Custom]
        public int Id { get; set; }
        public string Name { get; set; }
        [Custom]
        public void Study()
        {
            Console.WriteLine($"這裡是{this.Name}跟著Gerry老師學習");
        }
       
        [Custom(0)]
        public string Answer([Custom]string name)
        {
            return $"This is {name}";
        }
    }
}

AttributeUsage特性,影響編譯器執行,指定修飾的物件、能否重複修飾、修飾的特性子類是否生效,建議是明確約束用在哪些物件的

[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class|AttributeTargets.Property, AllowMultiple = true)]

4、特性可以指定屬性和欄位


using System;

namespace MyAttribute
{

    [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
    public class CustomAttribute : Attribute
    {
        public CustomAttribute()
        {
            Console.WriteLine($"{this.GetType().Name} 無引數建構函式執行");
        }
        public CustomAttribute(int id)
        {
            Console.WriteLine($"{this.GetType().Name} int引數建構函式執行");
            this._Id = id;
        }
        public CustomAttribute(string name)
        {
            Console.WriteLine($"{this.GetType().Name} string引數建構函式執行");
            this._Name = name;
        }

        private int _Id = 0;
        private string _Name = null;

        public string Remark;
        public string Description { get; set; }
    }
}

using System;

namespace MyAttribute
{
    [Custom(Remark = "123")]
    [Custom(Remark = "123", Description = "456")]
    [Custom(0, Remark = "123")]
    [Custom(0, Remark = "123", Description = "456")]
    public class Student
    {        
        public int Id { get; set; }
        public string Name { get; set; }
        
        public void Study()
        {
            Console.WriteLine($"這裡是{this.Name}跟著Gerry老師學習");
        }        
    }
}

5、特性還可以修飾返回值和引數


using System;

namespace MyAttribute
{
    public class Student
    {
        [return: Custom]
        public string Answer([Custom]string name)
        {
            return $"This is {name}";
        }
    }
}

6、多重修飾既可以中括號隔開,也可以一箇中括號裡面逗號隔開


using System;

namespace MyAttribute
{
    [Custom]
    [Custom()]
    [Custom(Remark = "123")]
    [Custom(Remark = "123", Description = "456")]
    [Custom(0)]
    [Custom(0, Remark = "123")]
    [Custom(0, Remark = "123", Description = "456")]
    public class Student
    {
        [return: Custom, Custom,Custom(), Custom(0, Remark = "123", Description = "456")]        
        public string Answer(string name)
        {
            return $"This is {name}";
        }
    }
}

四、特性應用案例

1、特性實現列舉展示描述資訊

(1)建立列舉類


namespace MyAttribute.EnumExtend
{
    /// <summary>
    /// 使用者狀態
    /// </summary>
    public enum UserState
    {
        /// <summary>
        /// 正常狀態
        /// </summary>
        [Remark("正常狀態")]
        Normal = 0,
        /// <summary>
        /// 已凍結
        /// </summary>
        [Remark("已凍結")]
        Frozen = 1,
        /// <summary>
        /// 已刪除
        /// </summary>
        [Remark("已刪除")]
        Deleted = 2
    }
}

(2)建立特性類


using System;

namespace MyAttribute.EnumExtend
{
    /// <summary>
    /// Remark特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Field)]
    public class RemarkAttribute : Attribute
    {
        public string Remark { get; private set; }
        public RemarkAttribute(string remark)
        {
            this.Remark = remark;
        }
    }
}

(3)列舉擴充套件方法


using System;
using System.Reflection;

namespace MyAttribute.EnumExtend
{
    public static class AttributeExtend
    {
        public static string GetRemark(this Enum value)
        {
            Type type = value.GetType();
            var field = type.GetField(value.ToString());
            if (field.IsDefined(typeof(RemarkAttribute), true))
            {
                RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true);
                return attribute.Remark;
            }
            else
            {
                return value.ToString();
            }
        }
    }
}

2、特性實現資料驗證

(1)基類抽象特性


using System;

namespace MyAttribute.ValidateExtend
{
    public abstract class AbstractValidateAttribute : Attribute
    {
        public abstract bool Validate(object oValue);
    }
}

(2)子類特性實現–數字長度


using System;

namespace MyAttribute.ValidateExtend
{
    [AttributeUsage(AttributeTargets.Property)]
    public class LongAttribute : AbstractValidateAttribute
    {
        private long _Min = 0;
        private long _Max = 4;
        public LongAttribute(long min, long max)
        {
            this._Min = min;
            this._Max = max;
        }

        public override bool Validate(object oValue)
        {
            return oValue != null
                && long.TryParse(oValue.ToString(), out long lValue)
                && lValue >= this._Min
                && lValue <= this._Max;
        }
    }
}

(3)子類特性實現–可空


namespace MyAttribute.ValidateExtend
{
    public class RequiredAttribute : AbstractValidateAttribute
    {
        public override bool Validate(object oValue)
        {
            return oValue != null
                && !string.IsNullOrWhiteSpace(oValue.ToString());
        }
    }
}

(4)子類特性實現–字串長度


using System;

namespace MyAttribute.ValidateExtend
{
    [AttributeUsage(AttributeTargets.Property)]
    public class StringLengthAttribute : AbstractValidateAttribute
    {
        private int _Min = 0;
        private int _Max = 3;
        public StringLengthAttribute(int min, int max)
        {
            this._Min = min;
            this._Max = max;
        }

        public override bool Validate(object oValue)
        {
            return oValue != null
                && oValue.ToString().Length >= this._Min
                && oValue.ToString().Length <= this._Max;
        }
    }
}

(5)泛型擴充套件方法


using System;

namespace MyAttribute.ValidateExtend
{
    public static class AttributeExtend
    {
        public static bool Validate<T>(this T t)
        {
            Type type = t.GetType();
            foreach (var prop in type.GetProperties())
            {
                if (prop.IsDefined(typeof(AbstractValidateAttribute), true))
                {
                    object oValue = prop.GetValue(t);
                    foreach (AbstractValidateAttribute attribute in prop.GetCustomAttributes(typeof(AbstractValidateAttribute), true))
                    {
                        if (!attribute.Validate(oValue))
                            return false;
                    }
                }
            }
            return true;
        }
    }
}

(6)常規類欄位定義


using System;

namespace MyAttribute.ValidateExtend
{
    public static class AttributeExtend
    {
        public static bool Validate<T>(this T t)
        {
            Type type = t.GetType();
            foreach (var prop in type.GetProperties())
            {
                if (prop.IsDefined(typeof(AbstractValidateAttribute), true))
                {
                    object oValue = prop.GetValue(t);
                    foreach (AbstractValidateAttribute attribute in prop.GetCustomAttributes(typeof(AbstractValidateAttribute), true))
                    {
                        if (!attribute.Validate(oValue))
                            return false;
                    }
                }
            }
            return tue;
        }
    }
}

(7)類呼叫擴充套件方法驗證欄位

using MyAttribute.EnumExtend;
using MyAttribute.ValidateExtend;
using System;

namespace MyAttribute
{
    /// <summary>
    /// main方法呼叫
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            try
            {                
                #region 特性實現資料驗證,並且可擴充套件
                {
                    //透過特性去提供額外行為
                    //資料驗證--到處都需要驗證
                    StudentVip student = new StudentVip()
                    {
                        Id = 123,
                        Name = "無為",
                        QQ = 729220650,
                        Salary = 1010000
                    };                    

                    if (student.Validate())
                    {
                        Console.WriteLine("特性校驗成功");
                    }
                    //1 可以校驗多個屬性
                    //2 支援多重校驗
                    //3 支援規則的隨意擴充套件
                }
                #endregion
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.Read();
        }
    }
}