[C#]Attribute特性(2)——方法的特性及特性参数

简介:

 上篇博文[C#]Attribute特性介绍了特性的定义,类的特性,字段的特性,这篇博文将介绍方法的特性及特性参数相关概念。

3.方法的特性 

      之所以将这部分单列出来进行讨论,是因为对方法的特性查询的反射代码不同于对类的特性查询的反射代码。在这个例子里,我们将使用一个特性用来定义一种可进行事务处理的方法。   

1 public class TransactionableAttribute : Attribute
2     {
3         public TransactionableAttribute() { }
4     }
复制代码
 1 public class TestClass
 2     {
 3         [Transactionable]
 4         public void Foo()
 5         { }
 6         public void Bar()
 7         { }
 8         [Transactionable]
 9         public void Baz()
10         { }
11     }
复制代码
复制代码
 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Type type = typeof(TestClass);
 6             foreach (MethodInfo method in type.GetMethods())
 7             {
 8                 foreach (Attribute attr in method.GetCustomAttributes())
 9                 {
10                     if (attr is TransactionableAttribute)
11                     {
12                         Console.WriteLine("{0} is transactionable.", method.Name);
13                     }
14                 }
15             }
16             Console.Read();
17         }
18     }
复制代码

    代码输出结果为:

    

      在这个特殊的例子,仅仅凭借TransactionableAttribute就可以让代码知道具有这种特性的方法可以参与事务处理。这也是为什么在这里只有一个简单的、不带参数的构造函数,而没有其他成员。然后TestClass定义三种方法:Foo方法、Bar方法和Baz方法;其中Foo方法和Baz方法方法被定义为具有处理事务能力的方法。请注意您附加一个带构造函数的特性时,如果这个构造函数不带参数,您就不需要把左括号和右括号包括进去了。

      现在让我们看看这个程序当中指的关注的部分,看看怎样通过方法的特性来查询类的方法。我们开始先用typeof来获得TestClass类的System.Type对象。

1 Type type = typeof(TestClass);

       然后我们使用Type.GetMethods方法来得到一个MethodInfo对象数组。每一个这样的对象包括TestClass类的一个方法信息。我们用foreach语句来循环处理我们的每一个方法。

1  foreach (MethodInfo method in type.GetMethods())

    现在我们有一个MethodInfo对象,我们就可以使用MethodInfo.GetCustomAttributes()方法来得到所有的用户创建的方法特性。我们还是使用foreach语句来循环处理返回的对象数组。

1 foreach (Attribute attr in method.GetCustomAttributes())

       在代码的这个地方,我们的方法有了特性。现在,通过使用is操作符,我们来判断一个特性是不是一个TransactionableAttribute,如果他是,就打印出这个方法的名字。

1 if (attr is TransactionableAttribute)
2  {
3     Console.WriteLine("{0} is transactionable.", method.Name);
4  }

特性的参数

      在上面的例子中,通过构造函数我们讨论了附加特性的使用。现在我们要来看看在前面没有谈到的特性的构造函数的一些方面。

定位参数和命名参数

      在上一篇博文字段的特性的例子中,您看到一个名为RefistryKeyAttribute的特性。它的构造函数形式如下:

1 public RegistryKeyAttribute(RegistryHives Hive, string valueName) 

      在这个构造函数声明之后,通过如下这种形式特性就附加给了一个字段:

1  [RegistryKey(RegistryHives. HKEY_CURRENT_USER,"Foo")]
2    public string Foo;

       到此为止,这些都很容易理解。这个构造函数有两个参数,这两个参数都是在把一个特性附加给一个字段时用到的。不过,我们可以让这种编程更简单。如果这个参数大多数时候都不变,那为什么每次都要让使用这个类的用户再费劲地输入这些参数呢?我们可以使用定位参数(position parameter)和命名参数来给这些参数设置默认值。

     定位参数是用在构造函数中的参数。在每次使用特性时它们是必须的参数,并且要必须指明这些参数。在上面的RegistryKeyAttribute例子中,Hive和ValueName都是定位参数。命名参数在特性的构造函数中实际上并没有定义,更确切地说,它们是非静态的字段和属性。因此,在一个特性被实例化时,命名参数让客户端能够设置这个特性的字段和属性,而不必让您为客户端要设置的每一种字段和属性的可能的组合而创建构造函数。

     每一个公共的构造函数都可以定义一系列的定位参数,就像所有类型的类一样。但是,对于特性来说,一旦它的定位参数被确定,用户就可以使用FieldOrPropertyName=Value来对某个字段或属性进行引用。下面我们通过对特性RegistryKeyAttribute的修改来解释一下这种情况。在这个例子里,我们取RegistryKeyAttribute.ValueName作为一个定位参数,而RegistryKeyAttribute.Hive就成了可选的命名参数。接下来,问题就是“您怎样才能把一些参数定义为命名参数?”因为只有定位参数——即必需性的——参数才包括在构造函数的定义中,因此我们只需简单地把这个可选参数从构造函数的定义中删除即可。然后用户就可以引用如下部分作为命名参数:非只读、静态或常量的任何字段,或包括设置存取器方法或非静态的setter的任何属性。因此,为了使RegistryKeyAttribute.Hive成为一个命名参数,我们要把它从构造函数的定义中删除,因为它作为一个公共的读/写属性已经存在了。

public RegistryKeyAttribute(string valueName) 

     用户现在就可以用下面的任一种方法来附加特性了:

1 [RegistryKey(“Foo”)]
2 [RegistryKey(“Foo”,Hive=RegistryHives.HKEY_LOCAL_MACHINE)]

      采用这种方式具有很好的灵活性,您既可以使用字段的默认值,同时,又可以让用户能够在需要的时候用其他值覆盖原来的默认值。但是要记住:如果用户没有设置RegistryKeyAttribute.Hive字段的值,我们怎样来默认它?您也许会想到,“哦,我们来检查一下看它是不是在构造函数中设置了。”但是,问题是RegistryKeyAttribute.Hive是一个enum型的,它的底层数据类型是int型——它是一个数值。这就意味着在定义时编译器已经把它初始化为0了!如果我们测试一下构造函数中RegistryKeyAttribute.Hive的值就会发现它等于0,我们不知道,是由调用程序通过命名参数设置的那个值,还是由编译器在编译时,因为它是一个数值型才给它设置了该值。不幸的是,现在能解决这个问题的唯一的途径是改变这段代码,让它的值为0时无效。这可以通过如下改变RegistryHives enum的方式实现: 

复制代码
1     public enum RegistryHives
2     {
3         HKEY_CLASSES_ROOT=1,
4         HKEY_CURRENT_USER,
5         HKEY_LOCAL_MACHINE,
6         HKEY_USERS,
7         HKEY_CURRENT_CONFIG
8     }
复制代码

      现在我们知道,使RegistryKeyAttribute.Hive为0的唯一途径是编译器把它初始化为0,并且用户没有通过一个命名参数来覆盖它的初值。我们可以用类似下面的代码来将其初始化:

复制代码
1         public RegistryKeyAttribute(string valueName)
2         {
3             this.valueName = valueName;
4             if (this.Hive == 0)
5             {
6                 this.hive = RegistryHives.HKEY_CURRENT_USER;
7             }
8         }
复制代码

使用命名参数时的常见错误

      当您使用命名参数时,您必须首先要指定定位参数。之后,由于命名参数是放在字段名或属性之前的,因此命名参数之间没有先后顺序。下面的例子将导致一个编译错误。

1 //This is an error because postional parameters can't follow
2 //named parameters
3 [RegistryKey(Hive=RegistryHives.HKEY_LOCAL_MACHINE, "Foo")]

      另外,您不能命名定位参数。编译器在编译特性的使用时,它会试着先去解析那些命名参数。然后再试着根据方法特性去解析剩下的——定位参数值。本段下面的代码无法通过编译,因为在要解析的参数当中必须半酣至少一个定位参数,如果全部都是命名参数,就会出现提示“No overload for method 'Registrykey Attribute 'takes ' O' arguments”(RegistrykeyAttribute方法没有参数值,无法进行重载)。

1 [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特性和特性标识符。敬请期待......

 

 

 

博客地址: http://www.cnblogs.com/wolf-sun/
博客版权: 本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。
如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步!
再次感谢您耐心的读完本篇文章。

转载:http://www.cnblogs.com/wolf-sun/p/3392765.html
目录
相关文章
|
2月前
|
开发框架 .NET 程序员
C# 去掉字符串最后一个字符的 4 种方法
在实际业务中,我们经常会遇到在循环中拼接字符串的场景,循环结束之后拼接得到的字符串的最后一个字符往往需要去掉,看看 C# 提供了哪4种方法可以高效去掉字符串的最后一个字符
245 0
|
1天前
|
编译器 C# 开发者
C# 9.0 新特性解析
C# 9.0 是微软在2020年11月随.NET 5.0发布的重大更新,带来了一系列新特性和改进,如记录类型、初始化器增强、顶级语句、模式匹配增强、目标类型的新表达式、属性模式和空值处理操作符等,旨在提升开发效率和代码可读性。本文将详细介绍这些新特性,并提供代码示例和常见问题解答。
12 7
C# 9.0 新特性解析
|
1月前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
114 65
|
2月前
|
编译器 C# Android开发
震惊!Uno Platform 与 C# 最新特性的完美融合,你不可不知的跨平台开发秘籍!
Uno Platform 是一个强大的跨平台应用开发框架,支持 Windows、macOS、iOS、Android 和 WebAssembly,采用 C# 和 XAML 进行编程。C# 作为其核心语言,持续推出新特性,如可空引用类型、异步流、记录类型和顶级语句等,极大地提升了开发效率。要在 Uno Platform 中使用最新 C# 特性,需确保开发环境支持相应版本,并正确配置编译器选项。通过示例展示了如何在 Uno Platform 中应用可空引用类型、异步流、记录类型及顶级语句等功能,帮助开发者更好地构建高效、优质的跨平台应用。
177 59
|
19天前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
28 1
|
1月前
|
存储 C#
【C#】大批量判断文件是否存在的两种方法效率对比
【C#】大批量判断文件是否存在的两种方法效率对比
32 1
|
1月前
|
C#
C#的方法的参数传递
C#的方法的参数传递
13 0
|
1月前
|
数据可视化 程序员 C#
C#中windows应用窗体程序的输入输出方法实例
C#中windows应用窗体程序的输入输出方法实例
39 0
|
2月前
|
C#
C#一分钟浅谈:Lambda 表达式和匿名方法
本文详细介绍了C#编程中的Lambda表达式与匿名方法,两者均可用于定义无名函数,使代码更简洁易维护。文章通过基础概念讲解和示例对比,展示了各自语法特点,如Lambda表达式的`(parameters) => expression`形式及匿名方法的`delegate(parameters)`结构。并通过实例演示了两者的应用差异,强调了在使用Lambda时应注意闭包问题及其解决策略,推荐优先使用Lambda表达式以增强代码可读性。
39 8
|
3月前
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
73 0