目录
更新
之前的关于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如下:
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还是比较好用。