zoukankan      html  css  js  c++  java
  • 16.2 【C# 5】调用者信息特性

    16.2.1 基本行为

      .NET 4.5引入了三个新特性(attribute),即 CallerFilePathAttribute 、 CallerLineNumber- Attribute 和 CallerMemberNameAttribute 。 三 者 均 位 于 System.Runtime.Compiler- Services 命名空间下。和其他特性一样,在应用时可以省略 Attribute 后缀。鉴于这是最常见的 特性用法,本书后续内容会进行适当地缩写。 这三个特性都只能应用于参数,并且只有在应用于可选参数时才有用。其理念非常简单:如 果调用点没有提供实参,则编译器可使用当前文件、行数或成员名来作为实参,而不使用常规的 默认值。如果调用者提供了实参,编译器则将忽略这些特性。

     1         static void Main(string[] args)
     2         {
     3             ShowInfo();
     4             ShowInfo("fileName", -10);
     5             Console.ReadKey();
     6         }
     7         static void ShowInfo([CallerFilePath] string file = null, [CallerLineNumber] int line = 0, [CallerMemberName]string member = null)
     8         {
     9             Console.WriteLine("{0}:{1} - {2}", file, line, member);
    10         }

    当然,并不需要总是为这些参数提供虚拟值,但显式传递还是很有用的,尤其是想使用同样的特性来记录当前方法调用者的时候。成员名特型适用于所有成员 ,但下列成员将使用特殊的名称:
     静态构造函数: .cctor ;
     构造函数: .ctor ;
     析构函数: Finalize 。
    当字段初始化器与字段名称相同时,该名称将作为方法调用的一部分。

      在两种情况下调用者成员信息不会生效。其一是特性初始化。代码清单16-3给出了一个特性 示例,希望可以得到其应用到的成员名称,但遗憾的是编译器在这种情况下不会自动完成任何信息的填充。

    1     public class MemberDescriptionAttribute : Attribute
    2     {
    3         public string Member { get; set; }
    4         public MemberDescriptionAttribute([CallerMemberName]string member = null)
    5         {
    6             Member = member;
    7         }
    8     }

      这本可以很有用。我曾多次见过开发者通过反射得到特性后,却不得不自己维护一个数据结 构,以保存成员名和特性之间映射的例子,而这本可以由编译器自动完成。 特性对动态类型无效,这是可以原谅的。代码清单16-4展示了不能生效的情况。

     1         static void Main(string[] args)
     2         {
     3             dynamic x = new TypeUsedDynamically();
     4             x.ShowCaller();
     5             Console.ReadKey();
     6         }
     7         class TypeUsedDynamically
     8         {
     9             internal void ShowCaller([CallerMemberName] string caller = "Unknown")
    10             {
    11                 Console.WriteLine("Called by: {0}", caller);
    12             }
    13         }

      代码清单16-4只打印出了 Called by: Unknown ,仿若应用特性不存在一般。尽管看上去有点遗憾,但要想让它生效,编译器需在每个可能需要调用者信息的动态调用处都内嵌上成员名、文件名和行数。总的来说,这对大多数开发者来说都是得不偿失的。

    16.2.2 日志

      调用者信息最明显的用途莫过于写入日志文件。以前记日志时,通常需要构造一个堆栈跟踪 (如使用 System.Diagnostics.StackTrace )来查找日志信息的出处。虽然它通常隐藏在日志 框架的后台,但依然无法改变其丑陋的存在。此外,它还可能存在性能问题,并且在JIT编译器 内联时十分脆弱。

      不难想象日志框架会如何使用这个新特性,来低廉地记录调用者信息,即使某些程序集可能 通过剥离调试信息或混淆操作来保护行数和成员名也无妨。当然,想记录完整的堆栈跟踪时,由 于该特性起不到什么作用,因此需各位自行实现这一操作。

      截至本书编写之时,还没有日志框架使用过该特性。首先它需要面向.NET 4.5进行构建, 或者像16.2.4节介绍的那样,需要显式声明这些特性。不过为自己喜欢的日志框架编写一个包 装类,并提供调用者信息还是很容易的。随着时间的推移,我敢肯定所有日志框架最终都会提 供此种功能。

     1     [AttributeUsage(AttributeTargets.All)]
     2     public class MemberDescriptionAttribute : Attribute
     3     {
     4         public MemberDescriptionAttribute([CallerMemberName] string member = null)
     5         {
     6             Member = member;
     7         }
     8 
     9         public string Member { get; set; }
    10     }
    11 
    12     [Description("Listing 16.3")]
    13     [MemberDescription]
    14     class MemberNames
    15     {
    16         static MemberNames()
    17         {
    18             Log("Static constructor");
    19         }
    20 
    21         public event EventHandler DummyEvent
    22         { 
    23             add { Log("Event add"); }
    24             remove { Log("Event remove"); }
    25         }
    26 
    27         static string foo = Log("Static variable initializer (foo)");
    28 
    29         string bar = Log("Instance variable initializer (bar)");
    30 
    31         private string this[int x] { get { return Log("Indexer"); } }
    32 
    33         private string Property
    34         { 
    35             get { return Log("Property get"); } 
    36             set { Log("Property set"); }
    37         }
    38     
    39         private void Method() { Log("Method"); }
    40 
    41         MemberNames()
    42         {
    43             Log("Constructor");
    44         }
    45         
    46         ~MemberNames()
    47         {
    48             Log("Finalizer");
    49         }
    50 
    51         static void Main()
    52         {
    53             var instance = new MemberNames();
    54             instance.Property = instance[10] + instance.Property;
    55             EventHandler lambda = (sender, args) => Log("Lambda expression");
    56             lambda(null, EventArgs.Empty);
    57             instance.DummyEvent += lambda;
    58             instance.DummyEvent -= lambda;
    59             var attribute = (MemberDescriptionAttribute) typeof(MemberNames).GetCustomAttributes(typeof(MemberDescriptionAttribute), false)[0];
    60             Console.WriteLine("Attribute on type: {0}", attribute.Member);
    61 
    62             instance = null;
    63             GC.Collect();
    64             GC.WaitForPendingFinalizers();
    65         }
    66 
    67         static string Log(string message, [CallerMemberName] string member = null)
    68         {
    69             Console.WriteLine("{0}: {1}", message, member);
    70             return null; // Just for the variable initializers
    71         }
    72     }
    View Code

    16.2.3 实现 INotifyPropertyChanged

      三大特性之一的 [CallerMemberName] 还有一个不太明显的用途,不过如恰好需要经常实 现 INotifyPropertyChanged 的话,这种用法就显而易见了。

      该接口十分简单,只包含一个类型为 PropertyChangedEventHandler 的事件。其委托类 型签名如下:

        public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

      PropertyChangedEventArgs 包含单一的构造函数:

            public PropertyChangedEventArgs(string propertyName);

      在C# 5之前,通常按以下方式实现 INotifyPropertyChanged 。

     1     class OldPropertyNotifier : INotifyPropertyChanged
     2     {
     3         public event PropertyChangedEventHandler PropertyChanged;
     4 
     5         private int firstValue;
     6         public int FirstValue
     7         {
     8             get { return firstValue; }
     9             set
    10             {
    11                 if (value != firstValue)
    12                 {
    13                     firstValue = value;
    14                     NotifyPropertyChanged("FirstValue");
    15                 }
    16             }
    17         }
    18 
    19         // Other properties with the same pattern
    20 
    21         private void NotifyPropertyChanged(string propertyName)
    22         {
    23             PropertyChangedEventHandler handler = PropertyChanged;
    24             if (handler != null)
    25             {
    26                 handler(this, new PropertyChangedEventArgs(propertyName));
    27             }
    28         }
    29     }

      辅助方法可避免在每个属性中都加入空验证。当然,也可以将其实现为扩展方法,以避免在 每个实现类中都重复一遍。

      这不仅冗长(此点没有改变),而且脆弱。问题在于属性的名称( FirstValue )指定为字 符串字面量,而如果将属性名重构为其他名称,则很可能会忘记修改字符串字面量。幸运的话, 工具和测试会帮助我们找到错误,但这仍然很丑陋。

      在C# 5中,大部分代码仍然相同,但可在辅助方法中使用 CallerMemberName ,让编译器来 填充属性名,如代码清单16-6所示。

     1     class NewPropertyNotifier : INotifyPropertyChanged
     2     {
     3         public event PropertyChangedEventHandler PropertyChanged;
     4 
     5         private int firstValue;
     6         public int FirstValue
     7         {
     8             get { return firstValue; }
     9             set
    10             {
    11                 if (value != firstValue)
    12                 {
    13                     firstValue = value;
    14                     NotifyPropertyChanged();
    15                 }
    16             }
    17         }
    18 
    19         // Other properties with the same pattern
    20 
    21         private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    22         {
    23             PropertyChangedEventHandler handler = PropertyChanged;
    24             if (handler != null)
    25             {
    26                 handler(this, new PropertyChangedEventArgs(propertyName));
    27             }
    28         }
    29     }

      此处只展示了发生变化的代码,就这么简单。现在如改变属性的名称,编译器则可用新名称 进行替代。这并不是惊天动地的大改进,但却非常不错。

    16.2.4 在非.NET 4.5 环境下使用调用者信息特性

      与扩展方法一样,调用者信息特性也只是请求编译器在编译过程中进行代码的转换。该类特性并没有使用我们无法提供的信息,只是在使用时需格外小心。跟扩展方法一样,我们也可以在早期.NET版本中使用它们,只需自己声明这些特性即可,这就如同从MSDN中复制声明一样简单。这些特性本身不包含任何参数,所以在类声明中无须提供其他内容,但仍然要放在 System.Runtime.CompilerServices 命名空间中。

      C#编译器将按处理.NET 4.5中真正的调用者信息特性那样来处理用户提供的特性。这么做的 缺点是,用.NET 4.5编译同样的代码时会产生错误。此时只需移除手动创建的特性,以避免编译 器产生混淆即可。

    如果使用的是.NET 4、Silverlight 4/5或Windows Phone 7.5,还可使用 Microsoft.Bcl Nuget 包。包内提供了这些特性,以及其他期待中的有用类型。

      这就是有关C# 5的全部内容。

  • 相关阅读:
    Oracle Haip无法启动问题学习
    OGG-Veridata如何对比没有主键的表?
    除PerfDog之外,还有什么性能测试工具。
    test
    Android系统WiFi网络架构
    audit2allow 添加SELinux权限
    select、poll、epoll之间的区别总结
    属性问题展开的selinux权限介绍
    android property属性property_set()&& property_get() selinux权限问题
    关于网络&wifi基础内容
  • 原文地址:https://www.cnblogs.com/kikyoqiang/p/10140534.html
Copyright © 2011-2022 走看看