[C#]Attribute特性(2)——方法的特性及特性参数
上篇博文[C#]Attribute特性介绍了特性的定义,类的特性,字段的特性,这篇博文将介绍方法的特性及特性参数相关概念。
3.方法的特性
之所以将这部分单列出来进行讨论,是因为对方法的特性查询的反射代码不同于对类的特性查询的反射代码。在这个例子里,我们将使用一个特性用来定义一种可进行事务处理的方法。
public class TransactionableAttribute : Attribute
{
public TransactionableAttribute() { }
}
public class TestClass
{
[Transactionable]
public void Foo()
{ }
public void Bar()
{ }
[Transactionable]
public void Baz()
{ }
}
class Program
{
static void Main(string[] args)
{
Type type = typeof(TestClass);
foreach (MethodInfo method in type.GetMethods())
{
foreach (Attribute attr in method.GetCustomAttributes())
{
if (attr is TransactionableAttribute)
{
Console.WriteLine("{0} is transactionable.", method.Name);
}
}
}
Console.Read();
}
}
代码输出结果为:
在这个特殊的例子,仅仅凭借TransactionableAttribute就可以让代码知道具有这种特性的方法可以参与事务处理。这也是为什么在这里只有一个简单的、不带参数的构造函数,而没有其他成员。然后TestClass定义三种方法:Foo方法、Bar方法和Baz方法;其中Foo方法和Baz方法方法被定义为具有处理事务能力的方法。请注意您附加一个带构造函数的特性时,如果这个构造函数不带参数,您就不需要把左括号和右括号包括进去了。
现在让我们看看这个程序当中指的关注的部分,看看怎样通过方法的特性来查询类的方法。我们开始先用typeof来获得TestClass类的System.Type对象。
Type type = typeof(TestClass);
然后我们使用Type.GetMethods方法来得到一个MethodInfo对象数组。每一个这样的对象包括TestClass类的一个方法信息。我们用foreach语句来循环处理我们的每一个方法。
foreach (MethodInfo method in type.GetMethods())
现在我们有一个MethodInfo对象,我们就可以使用MethodInfo.GetCustomAttributes()方法来得到所有的用户创建的方法特性。我们还是使用foreach语句来循环处理返回的对象数组。
foreach (Attribute attr in method.GetCustomAttributes())
在代码的这个地方,我们的方法有了特性。现在,通过使用is操作符,我们来判断一个特性是不是一个TransactionableAttribute,如果他是,就打印出这个方法的名字。
if (attr is TransactionableAttribute)
{
Console.WriteLine("{0} is transactionable.", method.Name);
}
特性的参数
在上面的例子中,通过构造函数我们讨论了附加特性的使用。现在我们要来看看在前面没有谈到的特性的构造函数的一些方面。
定位参数和命名参数
在上一篇博文字段的特性的例子中,您看到一个名为RefistryKeyAttribute的特性。它的构造函数形式如下:
public RegistryKeyAttribute(RegistryHives Hive, string valueName)
在这个构造函数声明之后,通过如下这种形式特性就附加给了一个字段:
[RegistryKey(RegistryHives. HKEY_CURRENT_USER,"Foo")]
public string Foo;
到此为止,这些都很容易理解。这个构造函数有两个参数,这两个参数都是在把一个特性附加给一个字段时用到的。不过,我们可以让这种编程更简单。如果这个参数大多数时候都不变,那为什么每次都要让使用这个类的用户再费劲地输入这些参数呢?我们可以使用定位参数(position parameter)和命名参数来给这些参数设置默认值。
定位参数是用在构造函数中的参数。在每次使用特性时它们是必须的参数,并且要必须指明这些参数。在上面的RegistryKeyAttribute例子中,Hive和ValueName都是定位参数。命名参数在特性的构造函数中实际上并没有定义,更确切地说,它们是非静态的字段和属性。因此,在一个特性被实例化时,命名参数让客户端能够设置这个特性的字段和属性,而不必让您为客户端要设置的每一种字段和属性的可能的组合而创建构造函数。
每一个公共的构造函数都可以定义一系列的定位参数,就像所有类型的类一样。但是,对于特性来说,一旦它的定位参数被确定,用户就可以使用FieldOrPropertyName=Value来对某个字段或属性进行引用。下面我们通过对特性RegistryKeyAttribute的修改来解释一下这种情况。在这个例子里,我们取RegistryKeyAttribute.ValueName作为一个定位参数,而RegistryKeyAttribute.Hive就成了可选的命名参数。接下来,问题就是“您怎样才能把一些参数定义为命名参数?”因为只有定位参数——即必需性的——参数才包括在构造函数的定义中,因此我们只需简单地把这个可选参数从构造函数的定义中删除即可。然后用户就可以引用如下部分作为命名参数:非只读、静态或常量的任何字段,或包括设置存取器方法或非静态的setter的任何属性。因此,为了使RegistryKeyAttribute.Hive成为一个命名参数,我们要把它从构造函数的定义中删除,因为它作为一个公共的读/写属性已经存在了。
public RegistryKeyAttribute(string valueName)
用户现在就可以用下面的任一种方法来附加特性了:
[RegistryKey(“Foo”)]
[RegistryKey(“Foo”,Hive=RegistryHives.HKEY_LOCAL_MACHINE)]
采用这种方式具有很好的灵活性,您既可以使用字段的默认值,同时,又可以让用户能够在需要的时候用其他值覆盖原来的默认值。但是要记住:如果用户没有设置RegistryKeyAttribute.Hive字段的值,我们怎样来默认它?您也许会想到,“哦,我们来检查一下看它是不是在构造函数中设置了。”但是,问题是RegistryKeyAttribute.Hive是一个enum型的,它的底层数据类型是int型——它是一个数值。这就意味着在定义时编译器已经把它初始化为0了!如果我们测试一下构造函数中RegistryKeyAttribute.Hive的值就会发现它等于0,我们不知道,是由调用程序通过命名参数设置的那个值,还是由编译器在编译时,因为它是一个数值型才给它设置了该值。不幸的是,现在能解决这个问题的唯一的途径是改变这段代码,让它的值为0时无效。这可以通过如下改变RegistryHives enum的方式实现:
public enum RegistryHives
{
HKEY_CLASSES_ROOT=,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
现在我们知道,使RegistryKeyAttribute.Hive为0的唯一途径是编译器把它初始化为0,并且用户没有通过一个命名参数来覆盖它的初值。我们可以用类似下面的代码来将其初始化:
public RegistryKeyAttribute(string valueName)
{
this.valueName = valueName;
if (this.Hive == )
{
this.hive = RegistryHives.HKEY_CURRENT_USER;
}
}
使用命名参数时的常见错误
当您使用命名参数时,您必须首先要指定定位参数。之后,由于命名参数是放在字段名或属性之前的,因此命名参数之间没有先后顺序。下面的例子将导致一个编译错误。
//This is an error because postional parameters can't follow
//named parameters
[RegistryKey(Hive=RegistryHives.HKEY_LOCAL_MACHINE, "Foo")]
另外,您不能命名定位参数。编译器在编译特性的使用时,它会试着先去解析那些命名参数。然后再试着根据方法特性去解析剩下的——定位参数值。本段下面的代码无法通过编译,因为在要解析的参数当中必须半酣至少一个定位参数,如果全部都是命名参数,就会出现提示“No overload for method 'Registrykey Attribute 'takes ' O' arguments”(RegistrykeyAttribute方法没有参数值,无法进行重载)。
[RegistryKey(ValueName="Foo",Hive=RegistryHives.HKEY_LOCAL_MACHINE)]
最后,命名参数可以是任何公共的、可存取的字段或属性——包括setter方法——只要不是静态的或常量即可。
有效的特性参数类型
特性类的定位参数和命名参数的类型仅限于特性参数类型,这些包括:
- bool,byte,char,double,float,int,long,short,string
- System.Type
- object
- enum类型,前提是它或任何有它嵌套在里面的类型必须是公共的可存取类型——就像在那个使用RegistryHives枚举的例子中一样。
- 由上述的任何类型组成的一维数组。
因为有效的参数类型仅局限于上述列出来的类型,因此您不能把一个像类那样的数据结构传递给特性构造函数作为参数。这种限制很有意义,因为特性是在程序设计时附加上的,此时您并没有这个类(对象)的实例化的实例。使用上面列出来的这些有效类型,您就可以在程序设计时把他们的值固定下来,就是为什么能使用他们的原因。
结语
方法的特性和特性参数就介绍到这里,您如果想了解更多请参考《c#技术内幕》这本书,本文也是摘自这本书,记录在此,方便回顾,也分享给大家,希望能对您有所帮助。下篇将学习AttributeUsage特性和特性标识符。敬请期待......
[C#]Attribute特性(2)——方法的特性及特性参数的更多相关文章
- 如何在方法上贴上attribute(特性)捕捉方法的异常,来实现我们的需求
在方法上贴上attribute(特性)捕捉方法的异常,其实这么做也是为了在项目中不会大量使用try-cacth这样的语句,同时使我们的代码看起来更简洁,更直观,将逻辑业务分离使得后期维护方便.这里我们 ...
- 转:Java中子类是否可以继承父类的static变量和方法而呈现多态特性
原文地址:Java中子类是否可以继承父类的static变量和方法而呈现多态特性 静态方法 通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明 ...
- 乐字节-Java8新特性之方法引用
上一篇小乐介绍了<Java8新特性-函数式接口>,大家可以点击回顾.这篇文章将接着介绍Java8新特性之方法引用. Java8 中引入方法引用新特性,用于简化应用对象方法的调用, 方法引用 ...
- 【翻译自mos文章】ABMR:在asm 环境中測试Automatic Block Recover 特性的方法
ABMR:在asm 环境中測试Automatic Block Recover 特性的方法 參考原文: ABMR: How to test Automatic Block Recover Feature ...
- 【Javascript】—— 1 方法function的高级特性
本篇仅仅对于function作简单的讲解,在javascript中function不仅仅是方法,它其实是一个变量,因此拥有自己的属性,并且可以当做参数传递给其他的方法. 那么传统的方法,按照java的 ...
- C# 语法特性 - 匿名方法(C#2.0)
概述 匿名方法的本质其实就是委托. 编译后会生成委托对象,生成方法,然后把方法装入委托对象,最后赋值给声明的委托变量. (匿名方法可以省略参数:编译的时候会自动为这个方法按照委托签名的参数添加参数) ...
- C#特性:从自定义一个特性开始,谈谈什么是特性
作为C#新手中的一员,我刚开始接触特性时,那真是一脸冏逼啊,怎么想怎么查资料都没弄明白它到底是个什么东西,有的入门教程甚至都没讲特性和反射这些概念!相信很多人第一次接触到特性就是关于系列化的知识了. ...
- python中引入包的时候报错AttributeError: module 'sys' has no attribute 'setdefaultencoding'解决方法?
python中引入包的时候报错:import unittestimport smtplibimport timeimport osimport sysimp.reload(sys)sys.setdef ...
- Atitit python3.0 3.3 3.5 3.6 新特性 Python2.7新特性1Python 3_x 新特性1python3.4新特性1python3.5新特性1值得关注的新特性1Pyth
Atitit python3.0 3.3 3.5 3.6 新特性 Python2.7新特性1 Python 3_x 新特性1 python3.4新特性1 python3.5新特性1 值得关注的新特性1 ...
随机推荐
- 如何自定义Grunt任务
任务(Tasks)是grunt的核心概念,你所做的很多工作比如资源合并(concat).压缩(uglify)都是在配置任务.每次grunt运行的时候,你指定的一个或多个任务也在运行,如果你没有指定任务 ...
- POJ 3687 Labeling Balls()
Labeling Balls Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 9641 Accepted: 2636 Descri ...
- Hibernate占位符?和:及JPA
小结一下hibernate占位符. 1.最常见的?占位符. String hql = "select a from Apple a where a.color=? a.weight>? ...
- apache性能测试工具ab使用详解
下面我们对这些参数,进行相关说明.如下:-n在测试会话中所执行的请求个数.默认时,仅执行一个请求.-c一次产生的请求个数.默认是一次一个.-t测试所进行的最大秒数.其内部隐含值是-n 50000,它可 ...
- mrunit for wordcount demo
import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.had ...
- Storm 基础知识
分布式的实时计算框架,storm对于实时计算的意义类似于hadoop对于批处理的意义. Storm的适用场景: 1.流数据处理:storm可以用来处理流式数据,处理之后将结果写到某个存入中去. 2.持 ...
- 两种动态加载JavaScript文件的方法
两种动态加载JavaScript文件的方法 第一种便是利用ajax方式,第二种是,动静创建一个script标签,配置其src属性,经过把script标签拔出到页面head来加载js,感乐趣的网友可以看 ...
- 不会UML的程序员不是好构架师?
情况描述 我已经工作两年半, 参加过一个网页游戏项目和一个IOS应用项目, 自以为参与度非常高, 也经常涉及到底层引擎和主逻辑业务. 目前想更快的向构架师方向发展. 最近在看\<Learning ...
- UESTC 916 方老师的分身III --拓扑排序
做法: 如果有a<b的关系,则连一条a->b的有向边,连好所有边后,找入度为0的点作为起点,将其赋为最小的价值888,然后其所有能到的端点,价值加1,加入队列,删去上一个点,然后循环往复, ...
- HDU 4445 Crazy Tank --枚举
题意: n个物体从高H处以相同角度抛下,有各自的初速度,下面[L1,R1]是敌方坦克的范围,[L2,R2]是友方坦克,问从某个角度抛出,在没有一个炮弹碰到友方坦克的情况下,最多的碰到敌方坦克的炮弹数. ...