16.2 【C# 5】调用者信息特性
16.2.1 基本行为
.NET 4.5引入了三个新特性(attribute),即 CallerFilePathAttribute 、 CallerLineNumber- Attribute 和 CallerMemberNameAttribute 。 三 者 均 位 于 System.Runtime.Compiler- Services 命名空间下。和其他特性一样,在应用时可以省略 Attribute 后缀。鉴于这是最常见的 特性用法,本书后续内容会进行适当地缩写。 这三个特性都只能应用于参数,并且只有在应用于可选参数时才有用。其理念非常简单:如 果调用点没有提供实参,则编译器可使用当前文件、行数或成员名来作为实参,而不使用常规的 默认值。如果调用者提供了实参,编译器则将忽略这些特性。
static void Main(string[] args)
{
ShowInfo();
ShowInfo("fileName", -);
Console.ReadKey();
}
static void ShowInfo([CallerFilePath] string file = null, [CallerLineNumber] int line = , [CallerMemberName]string member = null)
{
Console.WriteLine("{0}:{1} - {2}", file, line, member);
}
当然,并不需要总是为这些参数提供虚拟值,但显式传递还是很有用的,尤其是想使用同样的特性来记录当前方法调用者的时候。成员名特型适用于所有成员 ,但下列成员将使用特殊的名称:
静态构造函数: .cctor ;
构造函数: .ctor ;
析构函数: Finalize 。
当字段初始化器与字段名称相同时,该名称将作为方法调用的一部分。
在两种情况下调用者成员信息不会生效。其一是特性初始化。代码清单16-3给出了一个特性 示例,希望可以得到其应用到的成员名称,但遗憾的是编译器在这种情况下不会自动完成任何信息的填充。
public class MemberDescriptionAttribute : Attribute
{
public string Member { get; set; }
public MemberDescriptionAttribute([CallerMemberName]string member = null)
{
Member = member;
}
}
这本可以很有用。我曾多次见过开发者通过反射得到特性后,却不得不自己维护一个数据结 构,以保存成员名和特性之间映射的例子,而这本可以由编译器自动完成。 特性对动态类型无效,这是可以原谅的。代码清单16-4展示了不能生效的情况。
static void Main(string[] args)
{
dynamic x = new TypeUsedDynamically();
x.ShowCaller();
Console.ReadKey();
}
class TypeUsedDynamically
{
internal void ShowCaller([CallerMemberName] string caller = "Unknown")
{
Console.WriteLine("Called by: {0}", caller);
}
}
代码清单16-4只打印出了 Called by: Unknown ,仿若应用特性不存在一般。尽管看上去有点遗憾,但要想让它生效,编译器需在每个可能需要调用者信息的动态调用处都内嵌上成员名、文件名和行数。总的来说,这对大多数开发者来说都是得不偿失的。
16.2.2 日志
调用者信息最明显的用途莫过于写入日志文件。以前记日志时,通常需要构造一个堆栈跟踪 (如使用 System.Diagnostics.StackTrace )来查找日志信息的出处。虽然它通常隐藏在日志 框架的后台,但依然无法改变其丑陋的存在。此外,它还可能存在性能问题,并且在JIT编译器 内联时十分脆弱。
不难想象日志框架会如何使用这个新特性,来低廉地记录调用者信息,即使某些程序集可能 通过剥离调试信息或混淆操作来保护行数和成员名也无妨。当然,想记录完整的堆栈跟踪时,由 于该特性起不到什么作用,因此需各位自行实现这一操作。
截至本书编写之时,还没有日志框架使用过该特性。首先它需要面向.NET 4.5进行构建, 或者像16.2.4节介绍的那样,需要显式声明这些特性。不过为自己喜欢的日志框架编写一个包 装类,并提供调用者信息还是很容易的。随着时间的推移,我敢肯定所有日志框架最终都会提 供此种功能。
[AttributeUsage(AttributeTargets.All)]
public class MemberDescriptionAttribute : Attribute
{
public MemberDescriptionAttribute([CallerMemberName] string member = null)
{
Member = member;
} public string Member { get; set; }
} [Description("Listing 16.3")]
[MemberDescription]
class MemberNames
{
static MemberNames()
{
Log("Static constructor");
} public event EventHandler DummyEvent
{
add { Log("Event add"); }
remove { Log("Event remove"); }
} static string foo = Log("Static variable initializer (foo)"); string bar = Log("Instance variable initializer (bar)"); private string this[int x] { get { return Log("Indexer"); } } private string Property
{
get { return Log("Property get"); }
set { Log("Property set"); }
} private void Method() { Log("Method"); } MemberNames()
{
Log("Constructor");
} ~MemberNames()
{
Log("Finalizer");
} static void Main()
{
var instance = new MemberNames();
instance.Property = instance[] + instance.Property;
EventHandler lambda = (sender, args) => Log("Lambda expression");
lambda(null, EventArgs.Empty);
instance.DummyEvent += lambda;
instance.DummyEvent -= lambda;
var attribute = (MemberDescriptionAttribute) typeof(MemberNames).GetCustomAttributes(typeof(MemberDescriptionAttribute), false)[];
Console.WriteLine("Attribute on type: {0}", attribute.Member); instance = null;
GC.Collect();
GC.WaitForPendingFinalizers();
} static string Log(string message, [CallerMemberName] string member = null)
{
Console.WriteLine("{0}: {1}", message, member);
return null; // Just for the variable initializers
}
}
16.2.3 实现 INotifyPropertyChanged
三大特性之一的 [CallerMemberName] 还有一个不太明显的用途,不过如恰好需要经常实 现 INotifyPropertyChanged 的话,这种用法就显而易见了。
该接口十分简单,只包含一个类型为 PropertyChangedEventHandler 的事件。其委托类 型签名如下:
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
PropertyChangedEventArgs 包含单一的构造函数:
public PropertyChangedEventArgs(string propertyName);
在C# 5之前,通常按以下方式实现 INotifyPropertyChanged 。
class OldPropertyNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; private int firstValue;
public int FirstValue
{
get { return firstValue; }
set
{
if (value != firstValue)
{
firstValue = value;
NotifyPropertyChanged("FirstValue");
}
}
} // Other properties with the same pattern private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
辅助方法可避免在每个属性中都加入空验证。当然,也可以将其实现为扩展方法,以避免在 每个实现类中都重复一遍。
这不仅冗长(此点没有改变),而且脆弱。问题在于属性的名称( FirstValue )指定为字 符串字面量,而如果将属性名重构为其他名称,则很可能会忘记修改字符串字面量。幸运的话, 工具和测试会帮助我们找到错误,但这仍然很丑陋。
在C# 5中,大部分代码仍然相同,但可在辅助方法中使用 CallerMemberName ,让编译器来 填充属性名,如代码清单16-6所示。
class NewPropertyNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; private int firstValue;
public int FirstValue
{
get { return firstValue; }
set
{
if (value != firstValue)
{
firstValue = value;
NotifyPropertyChanged();
}
}
} // Other properties with the same pattern private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
此处只展示了发生变化的代码,就这么简单。现在如改变属性的名称,编译器则可用新名称 进行替代。这并不是惊天动地的大改进,但却非常不错。
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的全部内容。
16.2 【C# 5】调用者信息特性的更多相关文章
- NET 4.5 中新增的特性调用者信息特性CallerMemberNameAttribute/CallerFilePathAttribute/CallerLineNumberAttribute
标题中所说的三个特性 CallerMemberNameAttribute / CallerFilePathAttribute / CallerLineNumberAttribute 我们统称为调用者信 ...
- C# 调用者信息特性(Attribute)
.NET 4.5中引用了三种特性(Attribute), 该特性允许获取调用者的当前编译器的执行文件名.所在行数与方法或属性名称. 命名空间 System.Runtime.CompilerServic ...
- React 16 服务端渲染的新特性
React 16 服务端渲染的新特性 React 16 中关于服务端渲染的新特性 快速介绍React 16 服务端渲染的新特性,包括数组.性能.流等 React 16 终于来了!
- C# 调用者信息获取
做日志组件时,常常会记录调用者信息,通常都是通过反射来获取相应信息.不过.Net 4.5引入了三个新的特性,即CallerFilePathAttribute,CallerLineNumberAttri ...
- C#基础知识---获取调用者信息
一.概述 C#5.0提供了一种新功能,可以利用特性和可选参数获得调用者的信息.这些特性信息包括CallerLineNumber.CallerFilePath和CallerMemberName. 二.D ...
- .NET使用StackTrace获取方法调用者信息
前言 在日常工作中,偶尔需要调查一些诡异的问题,而业务代码经过长时间的演化,很可能已经变得错综复杂,流程.分支众多,如果能在关键方法的日志里添加上调用者的信息,将对定位问题非常有帮助. 介绍 Stac ...
- English trip V1 - B 16. Giving Reasons 提供个人信息 Teacher:Lamb Key: Why/Because
In this lesson you will learn how to give reasons for something you've done. 课上内容(Lesson) Why do peo ...
- 16/7/8_PHP-对象的高级特性
对这个理解不太懂或者说 没有一个明确的用法,不知道该怎么使用,说到底还是不懂有什么用.我还是先把只是点复制过来 对象比较,当同一个类的两个实例的所有属性都相等时,可以使用比较运算符==进行判断,当需要 ...
- 在 .NET 4.0 中使用 .NET 4.5 中新增的特性(CallerMemberNameAttribute/CallerFilePathAttribute/CallerLineNumberAttribute)
介绍 标题中所说的三个特性 CallerMemberNameAttribute / CallerFilePathAttribute / CallerLineNumberAttribute 我们统称为调 ...
随机推荐
- iOS截取视频某一帧图片(关键帧,AVAssetImageGenerator)
获取第一帧图片 导入 AVFoundation.Framework.CoreMedia.Framework 实现代码例如以下: + (UIImage*) thumbnailImageForVideo: ...
- 【翻译自mos文章】使用aum( Automatic Undo Management) 时遇到 ORA-01555错误--- 原因和解决方式。
使用aum( Automatic Undo Management) 时遇到 ORA-01555错误--- 原因和解决方式. 參考原文: ORA-01555 Using Automatic Undo M ...
- POJ 2367:Genealogical tree(拓扑排序)
Genealogical tree Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 2738 Accepted: 1838 Spe ...
- Linux下一款可以使用命令行的pdf阅读器
Zathura是linux下一款用命令行控制打pdf阅读器,并且基本打使用方法和vim很相似.对于喜欢键盘操作的用户来说的确是一个不错的选择. ubuntu下的安装命令: sudo apt-get i ...
- HDOJ 题目2475 Box(link cut tree去点找祖先)
Box Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submi ...
- 谈谈c++纯虚函数的意义!
纯虚函数的存在有什么意义呢?相信大学假设有c++这么课程.在讲到纯虚函数时,必然会讲到纯虚函数是面向接口编程的基础. 如今和大家分享下纯虚函数设计的原由.目的.产生的效果. 现代软件project很庞 ...
- Java 并发编程(二)对象的公布逸出和线程封闭
对象的公布与逸出 "公布(Publish)"一个对象是指使对象可以在当前作用域之外的代码中使用.可以通过 公有静态变量.非私有方法.构造方法内隐含引用 三种方式. 假设对象构造完毕 ...
- WIZnet的网络产品怎样选型
文章来源:成都浩然 我们在选用WIZnet的网络产品的时候.面对诸多的器件不知怎样选择,这里介绍一些方法以帮助project师高速准确地选择产品. WIZnet的产品有一个共同的特性.那就硬件TCPI ...
- MySQL之自定义函数实例讲解
转自:https://www.2cto.com/database/201804/740205.html MySQL中已经有很多函数,如时间函数等,但是有时这些函数不能满足自己的设计需求,此时需要自定义 ...
- codevs1026商务旅行
1036 商务旅行 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题解 题目描述 Description 某首都城市的商人要经常到各城镇去做 ...