Attribute的应用场景探讨

1 预定义Attribute应用

1.1 控制XML序列化

通过给class中不同元素打上Attribute,可以在对该对象进行xml序列化时,指定xml标签的值。例程如下:

using System;
using System.IO;
using System.Xml.Serialization;

namespace ConsoleApp1
{
    // 给元素标注信息
    [XmlRoot("部门")]
    public class Department
    {
        public string DeptName { get; set; }
        [XmlElement("附加信息")] 
        public string DeptExtraInfo { get; set; }
        // 允许非空元素
        [XmlElement(IsNullable = true)]
        public string DeptLocation { get; set; }
        
    }

    class Program
    {
        public static string SerializeXml(object data)
        {
            using (StringWriter sw = new StringWriter())
            {
                XmlSerializer xz = new XmlSerializer(data.GetType());
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add("", "");
                xz.Serialize(sw, data, ns);
                return sw.ToString();
            }
        }

        static void Main(string[] args)
        {
            Department dep = new Department()
            {
                DeptName = "abc", DeptExtraInfo = "xyz"
            };
            Console.WriteLine(SerializeXml(dep));
        }
    }
}

其输出如下:

<?xml version="1.0" encoding="utf-16"?>
<部门>
  <DeptName>abc</DeptName>
  <附加信息>xyz</附加信息>
  <DeptLocation d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance" />
</部门>

在此例程中,若未使用Attribute标注,则输出的xml元素信息为类名与变量名。同时,可以通过对元素标注一个允许为空的Attribute,使得在序列化xml的过程中该空元素不会被略过。

1.2 导入dll中的方法

通过DLLImport标注,可以为程序引入dll中的方法,例程如下:

using System;  
using System.Runtime.InteropServices;  
namespace attributes  
{  
    public class Test  
    {  
        // 导入user32.dll中的信息窗口模块
        [DllImport("user32.dll", EntryPoint = "MessageBox")]  
        public static extern int ShowMessageBox(int hWnd,string text, string caption,uint type);  
    }  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            string caption = "Hello World";  
            string text = "文本框使用Attribute从user32.dll中导入";  
            Test.ShowMessageBox(0, text, caption, 0);     
            Console.ReadKey();    
        }          
    }  
}

例程的运行结果如下:

image-20210721114341084

1.3 标注已过时的方法

通过Obsolete标注,能够让编译器知道被标注的方法已经过时,故而在调用此方法的时候抛出一个警告或错误信息,如例程所示:

using System;
using System.Runtime.InteropServices;

namespace attributes
{
    class Program
    {
        [Obsolete("Don't use OldMethod, use NewMethod instead", false)]
        static void OldMethod()
        {
            Console.WriteLine("It is the old method");
        }

        static void NewMethod()
        {
            Console.WriteLine("It is the new method");
        }

        public static void Main()
        {
            OldMethod();
        }
    }
}

在编译改程序时,编译器报错如下:

image-20210721120052169

通过这样的方式,在需要保留某些已经不应该被使用的接口时,可以通过指定编译器报错的情况来避免被调用,同时给使用者以通知。

2 自定义Attribute的扩展应用

2.1 Attribute在构建ORM中的应用

通过给class中的不同成员打上属性,可以让程序知道其在数据库中对应的字段名称,类型以及其他信息。利用这种信息,可以将对象与数据库关联起来,也就是ORM。例程如下:

using System;
using System.Reflection;

namespace ConsoleApp1
{
    // 自定义一个Attribute,用于存放数据字段的属性
    public class DataFieldAttribute : Attribute
    {
        private string _FieldName;
        private string _FieldType;
        public DataFieldAttribute(string fieldname, string fieldtype)
        {
            this._FieldName = fieldname;
            this._FieldType = fieldtype;
        }
        public string FieldName
        {
            get { return this._FieldName; }
            set { this._FieldName = value; }
        }
        public string FieldType
        {
            get { return this._FieldType; }
            set { this._FieldType = value; }
        }
    }
    
    // 用于测试自定义Attribute的类,其中每个字段被打上了不同的属性
    public class Person
    {
        [DataFieldAttribute("name", "nvarchar")]
        public string 姓名{ get; set; }
        [DataFieldAttribute("age", "int")]
        public int 年龄{ get; set; }
        [DataFieldAttribute("sex", "nvarchar")]
        public string 性别{ get; set; }
    }

    class program
    {
        static void Main()
        {
            Person person = new Person();
            person.姓名 = "小明";
            person.年龄 = 512;
            person.性别 = "male";
            // 通过反射来获取对象中各个字段的属性
            PropertyInfo[] infos = person.GetType().GetProperties();
            object[] objDataFieldAttribute = null;
            // 输出每个字段的属性
            foreach (PropertyInfo info in infos)
            {
                objDataFieldAttribute = info.GetCustomAttributes(typeof(DataFieldAttribute), false);
                Console.WriteLine(info.Name + " -> 该字段名称:" + ((DataFieldAttribute)objDataFieldAttribute[0]).FieldName + " 该字段属性: " + ((DataFieldAttribute)objDataFieldAttribute[0]).FieldType);
            }
        }
    }
}

其输出如下:

image-20210721152722668

在此例程中,通过构建一个自定义的的Attribute,实现了对于person类中不同元素的标签化,让程序在运行中可以通过反射来获取到其应该存放在数据库中的什么位置,以及其属性,从而为构建数据库中间层创造便利。

2.2 通过无法被继承的Attribute来标注类的信息

由于在定义Attribute时可以选择不被派生类继承,因此可以用来区别于Property来对类进行注解。如例程所示:

using System;
using System.Reflection;
using System.Text;

namespace ConsoleApp1
{
    // 自定义一个Attribute,用于存放附加信息,并指定其特性不可被继承
    [AttributeUsage(AttributeTargets.Class, Inherited=false)]
    public class PropertyAttribute : Attribute
    {
        public PropertyAttribute(string time, string info)
        {
            this.createdTime = time;
            this.debugInfo = info;
        }
        public string createdTime { get; set; }
        public string debugInfo { get; set; }
    }
    
    // 用于测试自定义Attribute的基类,被附加上了自定义的Attribute
    [PropertyAttribute("2021-7-1","test debug info 1")]
    public class Person
    {
        public string 姓名{ get; set; }    
        public int 年龄{ get; set; }
        public string 性别{ get; set; }
    }
    // 用于测试的派生类,其派生自定义过Attribute的类
    public class Kid : Person
    {
        public string 老师姓名 { get; set; }
    }

    class program
    {
        static void Main()
        {
            // 通过反射来获取对象中各个字段的属性
            var personInfo = typeof(Person).GetCustomAttributes(true);
            var kidInfo = typeof(Kid).GetCustomAttributes(true);

        }
    }
}

编译并执行完成后,如下图所示,可以在调试器中看到作为基类的person类被打上了标记,而派生类未继承其基类的标记。

image-20210721162828598

3 总结

没有什么所谓总结