C#中Attribute(特性)

更新

之前的关于C#中Attribute博客是在4年前的2018年1月9日发布的,当时是《CLR via C#》读书笔记。在实际工作中并未经常使用,因此基本上忘得差不多。前几天在学习NPOI时,又重新发现了这个Attribute挺有意思,所以再重新学习一下。

1、获取Attribute的设定值

Attribute是一系列设定值,所以设定完成后,一定要能够获取,这样设置了才有用。而使用最多的就是通过反射进行的。

1.1 使用反射获取设置值

下面是通过反射,实现了获取设定值

using System;
using System.Collections.Generic;
using System.Reflection;

namespace AttributeRefelectionConsole
{
    //
    class Program
    {
        static void Main(string[] args)
        {
            DataModel tempData=new DataModel(){ItemID="1",ItemName="ZL"};

            Program tempClass = new Program();

            var listdata = tempClass.getAttributeNames<DataModel>(tempData);


            listdata.ForEach(t => {

                Console.WriteLine(t);

            });
            
            Console.Read();
        }

        //获取设定类的Attribute的List
        public List<string> getAttributeNames<T>(T entity)
        {
            List<string> resultList = new List<string>();
            PropertyInfo[] pi = entity.GetType().GetProperties();

            foreach (var item in pi)
            {
            		//GetCustomAttributes返回的是一个object的数组
                var obj= item.GetCustomAttributes(typeof(DataSQLAttribute), false);

                if (obj != null)
                {
                	 //因为在DataModel的ItemID和ItemName属性上,每个属性只应用了1个Attribute
                	 //所以可以使用obj[0]这种写法。若在一个属性上有多个Attrbute,则需要进行判断
                   var displayname=  ((DataSQLAttribute)obj[0]).GetDisplayName();
                   resultList.Add(displayname);
                }
            }

            return resultList;
        }
    }



    //定义的数据Model
    public class DataModel
    {
        [DataSQLAttribute("第一个属性的特性设定值")]
        public string ItemID { get; set; }

        [DataSQLAttribute("第二个属性的特性设定值")]
        public string ItemName { get; set; }
    }

    //自定义的特性值
    public class DataSQLAttribute : Attribute
    {
        private string displayName;
        public DataSQLAttribute(string displayName)
        {
            this.displayName = displayName;
        }
        public string GetDisplayName()
        {
            return displayName;
        }
    }
}

因为在DataModel的属性只使用了一个Attribute,因此在getAttributeNames方法中,直接使用了(DataSQLAttribute)obj[0]强制类型转换。若属性中有多个Attribute,则需要进行判断。判断的代码如下:

//获取设定类的Attribute的List
public List<string> getAttributeNames<T>(T entity)
{
   List<string> resultList = new List<string>();
   PropertyInfo[] pi = entity.GetType().GetProperties();

   foreach (var item in pi)
   {
       object[] obj= item.GetCustomAttributes(typeof(DataSQLAttribute), false);

       if (obj != null)
       {
           //方法2,若有多个Attribute,需要进行判断的
           foreach (var objitem in obj)
           {
               //进行判断
               if (objitem is DataSQLAttribute)
               {
                   var displayname = ((DataSQLAttribute)obj[0]).GetDisplayName();
                   resultList.Add(displayname);
               }
           }
       }
   }

   return resultList;
}
1.2 代码下载

代码下载

1.3 使用Attribute实现ORM

可以使用Attribute实现一个ORM。代码如下:

//获取设定类的Attribute的List
public int AddPerson<T>(T entity)
{
    string oracleDataTableName = "";
    string[,] paraAndData = new string[3, 3];

    Type zlt = entity.GetType();

    //获取对应的表名
    object[] tableObj = zlt.GetCustomAttributes(typeof(TableAttribute), false);
    if(tableObj!=null)
    {
        oracleDataTableName= ((TableAttribute)tableObj[0]).getDataTableName();
    }

    //获取字段名称及属性值
    PropertyInfo[] pi = zlt.GetProperties();
    for (int i = 0; i < pi.Length; i++)
    {
        object[] obj = pi[i].GetCustomAttributes(typeof(FieldAttribute), false);

        if (obj != null)
        {
            FieldAttribute temp = ((FieldAttribute)obj[0]);

            paraAndData[i,0] = temp.GetDataColumnName();
            paraAndData[i, 1] = temp.GetDataColumnType();
            paraAndData[i, 2] = pi[i].GetValue(entity, null).ToString();
        }
    }


    //形成SQL,这个可以进行自己处理
    string sqlformat = @"insert into {0}({1},{2},{3}) values ('{4}','{5}',{6})";
    string sql = string.Format(sqlformat, oracleDataTableName, paraAndData[0, 0], paraAndData[1, 0], paraAndData[2, 0], paraAndData[0, 2],
        paraAndData[1, 2], paraAndData[2, 2]);

    return 1;
}

形成的SQL如下:
SQL

1.3 代码下载

代码的下载如下:下载链接

1.3 使用Attribute获取设置值

可以直接使用Attribute类进行操作

using System;
using System.Reflection;
using System.Diagnostics;


[assembly:CLSCompliant(true)]
namespace NETAttribute
{
    [Serializable]
    [DefaultMember("Main")]
    [DebuggerDisplay("Zheng",Name ="Lin",Target =typeof(Program))]
    public class Program
    {
        [Conditional("Debug")]
        [Conditional("Release")]
        public void SomeMethod() { }

        public Program() { }

        [CLSCompliant(true)]
        [STAThread]
        public static void Main(string[] args) //此处Main方法需为public,否则Main不会暴露,[DefaultMember("Main")]将无法应用到Main上
        {
            ShowAttributes(typeof(Program));

            MemberInfo[] members = typeof(Program).FindMembers(MemberTypes.Constructor | MemberTypes.Method,
                BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static,
                Type.FilterName, "*");

            foreach (MemberInfo item in members)
            {
                ShowAttributes(item);
            }

            Console.Read();
        }


        //获取当前类型中的Attribute设定值
        private static void ShowAttributes(MemberInfo attributeTarget)
        {
            //获取目标上的Attribute
            Attribute[] attributes = Attribute.GetCustomAttributes(attributeTarget);

            //展示目标信息
            Console.WriteLine("当前目标的名称是{0},本目标上的Attribute有:{1}", attributeTarget.Name, (attributes.Length == 0 ? "无" : string.Empty));


            //遍历Attributes并将信息展示
            foreach (Attribute item in attributes)
            {
                //显示Attribute的类型
                Console.WriteLine("Attribute的名称是{0}",item.GetType().ToString());

                if (item is DefaultMemberAttribute)
                    Console.WriteLine("MemberName={0}",((DefaultMemberAttribute)item).MemberName);
                if (item is ConditionalAttribute)
                    Console.WriteLine("ConditionString={0}",((ConditionalAttribute)item).ConditionString);
                if (item is CLSCompliantAttribute)
                    Console.WriteLine("IsCLSCompliant={0}",((CLSCompliantAttribute)item).IsCompliant);

                DebuggerDisplayAttribute dda = item as DebuggerDisplayAttribute;
                if (dda != null)
                {
                    Console.WriteLine("Value={0},Name={1},Target={2}", dda.Value, dda.Name, dda.Target);
                }
            }

            Console.WriteLine();
        }
    }
}

2、Attribute使用过程中的注意点

2.1、Attribute的构造器的参数

Attribute是一个类,因此使用时,需要向其传递构造器必须的参数,以及初始化一些属性或设定。构造器的必须参数,被称之为“定位参数”(Positional Parameter),顺手初始化的属性或公共字段的参数,称之为“命名参数”(Named Parameter)。前者是必须,后者是选择项。在VS使用中,智能提醒如下所示。里面明确的指出,哪些是必填的定位参数,哪些是选填的命名参数。

这里写图片描述

2.2、使用注意事项

可以将多个Attribute用于单个目标元素。Attribute的先后顺序无所谓。多个Attribute可以放在一个中括号内,也可以多个。另外,若Attribute的构造器不需要参数,则Attribute后面的圆括弧也可以删除。举例,以下都是相同的:

[Serializable][Flags]
[Serializable,Flags]
[SerializableAttribute,FlagsAttribute]
[FlagsAttribute(), SerializableAttribute()]
2.3 常用的Attribute

(一)常用的Attribute有:
1、DllImport应用于方法,是指告诉CLR,方法的实现位于制定路径的非托管DLL中。
2、Flags应用于枚举类型。告诉CLR,将枚举类型作为一个标志位集合使用
3、Serializable应用于类型。告诉序列格式化器,一个实例的字段可以序列化和反序列化。

具体详细的可以参见:MSDN上Attribute的介绍内容

(二)使用时的注意事项或语法
在Attribute使用前,需要有一些前缀。例如:[method:DllImport(“…”)],有时,前缀可以省略,编译器会自动判断。但有一些不能省略。以下是Attribute的语法。下面代码的带有“//前缀必须”代表不能删除该前缀

using System;

[assembly:SomeAttr] //前缀必须
[module:SomeAttr]   //前缀必须

namespace NETAttribute
{
    [type:SomeAttr]
    class AttributeGrammar<[typevar:SomeAttr] T>
    {
        [field:SomeAttr]
        public int SomeFiled = 0;

        [return: SomeAttr]  //前缀必须
        [method: SomeAttr]
        public int SomeMethod([param:SomeAttr]int someParam)
        {
            return SomeParam
        }

        [property: SomeAttr]
        public string SomeProp
        {
            [method: SomeAttr]
            get { return null; }
        }

        [event: SomeAttr]
        [field: SomeAttr]   //前缀必须
        [method: SomeAttr]  //前缀必须
        public event EventHandler SomeEvent;
    }
}
2.4 自定义Attribute注意事项

自定义Attribute需要满足两个条件
1、必须继承system.Attribute抽象类
2、所有非抽象的Attribute至少包含一个公共构造器
另外,根据“行规”,自定义的Attribute要以“Attribute”字母结尾。
原则
Attribute的本质是一个类,但这个类只是提供一些基本的状态信息,或用作逻辑状态的一个容器。不应该在该类中实现复杂的方法、逻辑,更不应该提供方法、事件或者其他成员。若需要这些,请单独编写一个逻辑类(class文件)。不要把Attribute搞得复杂。

3、其他

重构ShowAttributes方法

//获取当前类型中的Attribute设定值
private static void ShowAttributesBuReflection(MemberInfo attributeTarget)
{
    //获取目标上的Attribute
    IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(attributeTarget);

    //展示目标信息
    Console.WriteLine("当前目标的名称是{0},本目标上的Attribute有:{1}", attributeTarget.Name, (attributes.Count == 0 ? "无" : string.Empty));


    //遍历Attributes并将信息展示
    foreach (CustomAttributeData item in attributes)
    {

        Type t = item.Constructor.DeclaringType;

        Console.WriteLine("{0}", item.ToString());
        Console.WriteLine("构造器={0}", item.Constructor);

        IList<CustomAttributeTypedArgument> posArgs= item.ConstructorArguments;
        Console.WriteLine("是否存在定位参数={0}",(posArgs.Count==0)?"否":string.Empty);
        foreach (CustomAttributeTypedArgument m in posArgs)
        {
            Console.WriteLine("参数的类型:{0},参数值:{1}",m.ArgumentType,m.Value);
        }

        IList<CustomAttributeNamedArgument> namedArgs = item.NamedArguments;
        Console.WriteLine("是否存在命名参数={0}", (namedArgs.Count == 0) ? "否" : string.Empty);
        foreach (CustomAttributeNamedArgument m in namedArgs)
        {
            Console.WriteLine("参数名称:{0},参数的类型:{1},参数值:{2}", m.MemberInfo.Name,
                m.TypedValue.ArgumentType, m.TypedValue.Value);
        }
    }

    Console.WriteLine();
}

运行结果如下:
输出结果

4、总结

这个Attribute还是比较好用。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值