谈论“nameof”运算符。






4.96/5 (18投票s)
本文将讨论C# 6支持的“nameof”运算符。它允许将字符串映射到命名的代码元素。
引言
在某种程度上,本文是上一篇题为“如果名称包含空格会怎样?”的文章的延续。它是关于最新的C#和Java编程语言特性的系列文章的一部分。
本文将讨论C# 6支持的“nameof”运算符。它允许将字符串映射到命名的代码元素。由于某些原因,这是代码中非常有用的功能。为了理解其应用的主要领域,让我们考虑以下示例。
示例 1:异常处理
添加“nameof”的主要原因是异常处理。当抛出“ArgumentException”及其派生类时,您将使用字符串来表示无效参数的名称。不幸的是,这些字符串没有编译时验证,任何代码更改(如重命名类、方法、属性、字段和变量)都不会自动更新字符串,从而导致编译器从未捕获到的不一致。
void SomeMethod<T>(T value) {
if (value == null)
throw new ArgumentNullException(paramName: nameof(value));
if (!(typeof(T) is Iinterface))
throw new ArgumentException (nameof(T),
$"Type '{typeof(T)}' does not support the method" +
$"' { nameof(MyNamespace.MyClass)}.{nameof(SomeMethod)}'." );
//Output: Type 'SomeType' does not support the method 'MyClass.SomeMethod'.
}
也可以编写以下代码,尽管该方法不是静态的
$"Type '{typeof(T)}' does not support the method "+
$"'{nameof(MyNamespace.MyClass.SomeMethod)}'."
//Type 'MyNamespace.SomeType' does not support the method 'SomeMethod'.
我想在“nameof”中字面写“MyNamespace.MyClass.SomeMethod”,但编译器将我的字符串截断为“SomeMethod”。因此,该运算符名称完全符合预期行为。编写代码时应牢记这一点。
示例 2:可通知属性
C# 6新功能出版物中广泛出现的另一个原因是改进“OnPropertyChanging”和“OnPropertyChanged”事件引发的代码,这些事件属于“INotifyPropertyChanging”和“INotifyPropertyChanged”接口。这些接口通知客户端属性值正在更改或已更改。
public class SomeClass : INotifyPropertyChanging, INotifyPropertyChanged {
// From the INotifyPropertyChanging interface
public event PropertyChangingEventHandler PropertyChanging;
protected virtual void OnPropertyChanging(String propertyName) {
var handler = PropertyChanging;
if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));}
// From the INotifyPropertyChanged interface
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));}
public const String DEFAULT_SOME_TEXT = "Default value";
private String _someText = DEFAULT_SOME_TEXT;
[DefaultValue(DEFAULT_SOME_TEXT)]
public String SomeText {
get { return this._someText; }
set {
if ( String.Equals( this._someText, value ,
StringComparison.CurrentCultureIgnoreCase))
return;
// Old code:
OnPropertyChanging("SomeText");
// or (see NameOfExt extension class)
OnPropertyChanging(this.NameOf( o => SomeText ));
// New Code:
OnPropertyChanging(nameof(SomeText));
this ._someText = value ;
// Old code:
OnPropertyChanged("SomeText");
// or (see NameOfExt extension class)
OnPropertyChanged(this.NameOf( o => SomeText ));
// New Code:
OnPropertyChanged(nameof(SomeText));
}
}
}
该代码是一个很好的例子,说明了C# 6的新可能性有多么有用。几乎每个开发人员都编写过类似的代码,并且知道记住字符串中描述的属性名称有多么困难。但是,当“.NET 4.5”中出现了“System.Runtime.CompilerServices.CallerMemberNameAttribute”属性时,就不需要编写调用者属性的名称。此属性允许装饰方法参数,指示在调用方法时将相应的信息注入该参数。
protected virtual void OnPropertyChanging(
[CallerMemberName] String propertyName = "") { ... }
protected virtual void OnPropertyChanged(
[CallerMemberName] String propertyName = "") { ... }
...
// Old code:
// OnPropertyChanging("SomeInt");
// or (see NameOfExt extension class)
// OnPropertyChanging(this.NameOf(o=>SomeInt));
// New Code (C# 6):
// OnPropertyChanging(nameof(SomeInt));
// The better code without C# 6 feature
OnPropertyChanging();
this._someInt = value;
// Old code:
// OnPropertyChanged("SomeInt");
// or (see NameOfExt extension class)
// OnPropertyChanging(this.NameOf(o=>SomeInt));
// New Code (C# 6):
// OnPropertyChanged(nameof(SomeInt));
// The better code without C# 6 feature
OnPropertyChanged();
这些类在各种项目中都非常受欢迎。例如,WPF允许使用“INotifyPropertyChanged”接口。如果“DataContext”属性中的对象实现了该接口,WPF将监听其“PropertyChanged”事件。此事件会通知属性值的更改。当引发事件时,它会指示UI控件绑定已更改,并且显示的值也会相应更改。因此,存在更紧凑的解决方案。
[Serializable]
public class SomeClass : INotifyPropertyChanging, INotyfyPropertyChanged {
#pragma warning disable 0067 // The event raised by PropertyExt extensions
// From the INotifyPropertyChanging interface
[field: NonSerialized]
public event PropertyChangingEventHandler PropertyChanging;
// From the INotifyPropertyChanged interface
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore 0067
public const String DEFAULT_SOME_TEXT = "Default value";
private String _someText = DEFAULT_SOME_TEXT;
[DefaultValue(DEFAULT_SOME_TEXT)]
public String SomeText {
get { return this._someText; }
set { this.SetPropertyAndNotify(ref _someText, value); }
}
public const Int32 DEFAULT_SOME_INT = 123;
private Int32 _someInt = DEFAULT_SOME_INT;
[DefaultValue(DEFAULT_SOME_INT)]
public Int32 SomeInt {
get { return this._someInt; }
set { this.SetPropertyAndNotify(ref _someInt, value); }
}
}
扩展方法“SetPropertyAndNotify”可以实现如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
public static class PropertyExt {
public static void SetPropertyAndNotify(
this INotifyPropertyChanged source,
ref T field,
T value,
[CallerMemberName] String propertyName = "",
EqualityComparer equalityComparer = null)
{
if (source == null)
throw new ArgumentNullException("source");
if (!(equalityComparer ?? EqualityComparer.Default).Equals(field, value)) {
if (source is INotifyPropertyChanging) {
RaiseEvent(source, "PropertyChanging",
new PropertyChangingEventArgs(propertyName));
}
field = value;
RaiseEvent(source, "PropertyChanged",
new PropertyChangedEventArgs(propertyName));
}
}
private static void RaiseEvent(
this Object source,
String eventName,
TEventArgs eventArgs)
where TEventArgs : EventArgs
{
var eventDelegate = (MulticastDelegate)source.GetType()
.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(source);
var parameters = new Object[] { source, eventArgs };
foreach (var handler in eventDelegate.GetInvocationList()) {
handler.Method.Invoke(handler.Target, parameters);
}
}}
如前所述,在不需要使用“nameof”运算符的许多好解决方案中:“MVVM Light”(“NuGet PM: Install-Package MvvmLight”)、“AOP PostSharp”(“NuGet PM: Install-Package PostSharp”;另请参阅“AOP实现INotifyPropertyChanged”)、“Kind of Magic”(CodePlex,VS Gallery)。此外,在某些情况下,该运算符可能会导致编写低质量代码。
为了与现有代码兼容,可以通过基于表达式树的帮助程序方法来避免使用“nameof”运算符。
using System;
using System.Linq.Expressions;
namespace Example {
public static class NameOfExt {
public static String NameOf(Expression accessor) {
return InternalNameOf(accessor); }
public static String NameOf(Expression> accessor) {
return InternalNameOf(accessor); }
public static String NameOf(this T obj, Expression> accessor) {
return InternalNameOf(accessor); }
private static String InternalNameOf(Expression expression) {
if (expression == null )
throw new ArgumentNullException ( "expression" );
if (expression is LambdaExpression )
return _NameOf((( LambdaExpression ) expression).Body);
if (expression is UnaryExpression )
return NameOf((( UnaryExpression ) expression).Operand);
var memberExpression = expression as MemberExpression ;
if (memberExpression != null )
return memberExpression.Member.Name;
MethodCallExpression methodExpression = expression as MethodCallExpression ;
if (methodExpression != null )
return methodExpression.Method.Name;
throw new ArgumentException (
"The 'expression' should be a member expression or a method call expression." ,
"expression" );
}
}
internal class Program {
internal static void Main(string[] args) {
var str = "Some String";
// Gets the name of an object's property: " Length" .
Console .WriteLine(str.NameOf(o => o.Length));
// Gets the name of an object's method: " GetType" .
Console .WriteLine(str.NameOf(o => o.GetType()));
// Gets the name of a class' property: " Empty ".
Console .WriteLine( NameOfExt .NameOf(() => String .Empty));
// Gets the name of a class' method: "Copy" .
Console.WriteLine(NameOfExt.NameOf(() => String.Copy("")));
}}}
在“nameof”运算符出现之前,已经编写了类似的 C# 代码。但是,“nameof”在编译时成为字符串常量,而不是执行代码来通过表达式树或反射方法获取代码元素的名称。因此,引入的运算符是C#的一个巨大优势,但它也是一个劣势。JIT编译器的功能并未得到利用。它不允许预测动态代码更改并正确替换名称。此外,原则上没有机会将代码的一部分转换为字符串“按原样”来跟踪其后续更改:“System.Math.PI”、“Type Property”或“Type Namespace.Class.Method(Type arg0, Type arg1, ...)”。运算符的功能风格可能值得怀疑。我想问一个问题:参数的类型是什么?但问题消失了,因为已经存在诸如“typeof”之类的类似方法。此外,编译后,方法式运算符将被字符串替换。也许,扩展字符串插值功能以严格、字面地将代码部分转换为字符串并在编译时对其进行验证会更自然。
$"'\@{MyNamespace.SomType}', '\@{SomType arg0}', '\@{arr[index]}'."
//'MyNamespace.SomeType', 'SomType arg0', 'arr[index]'.
$"'\@{MyNamespace.{SomeType}}', '\@{{SomType} arg0}', '\@{{public }int {Prop}}'."
//'SomeType', 'SomeType', 'public Prop'. The braces used for the selective conversion
“nameof”运算符很可能成为插值字符串的一部分。所提供的可重构字符串语法可以方便地应用于注释中。
// This comment contains the name of @{MyNamespace.SomeType} type that
// sensible to the code refactoring.
在自文档化注释的情况下,部分提供了类似的功能。
/// <summary>
/// This method performs some operations on
/// <paramref name="x"/> and <paramref name="y"/> arguments.
/// </summary>
/// <param name="x">The first argument</param>
/// <param name="y">The second argument</param>
/// <returns></returns>
Double Method(Double x, Double y) { ... }
/// <summary>
/// The <code>SomeMethod</code> demonstates ...
/// </summary>
/// <typeparam name="T"> The template parameter </typeparam>
/// <param name="value"> The input argument </param>
void SomeMethod<T>(T value) {
// The 'value' parameter couldn't be null.
if (value == null )
... }
最后,让我们展示“nameof”在对象通知中的实际用法。基于前面的示例,您可能会问。当“INotifyPropertyChanged”接口的良好实现不包含它时,它在哪里有用?必须记住,“PropertyChanged”事件有订阅者。如果属性名称定义不正确,则无法执行预期的操作。
private static void NPCSubscriber_PropertyChanged(
Object sender,
PropertyChangedEventArgs e)
{
switch (e.PropertyName) {
case nameof(ISomeClass.SomeInt): // "SomeInt"
... // Perform action 1
break;
case nameof(ISomeClass.SomeText): // "SomeText"
... // Perform action 2
break;
default:
throw new InvalidOperationException();
}
}
设计“NPC”类(包含“Notify Property Changed”事件)时,应牢记这一点,运算符“nameof”在订阅者中定义的无法访问隐藏的成员。
class ClassWithHiddenMembers {
protected Int32 Protected { get { return _protected; } }
protected Int32 _protected;
}
class ClassForTestingOfPrivateMembersAccess {
public override string ToString()
=> $"{nameof(ClassWithHiddenMembers._protected)}, "
+ $"{nameof(ClassWithHiddenMembers.Protected)}";
// Error: It's inaccessible due to its protection level
}
“PropertyChanged”订阅者实现的示例非常有启发性。通知更改的属性被分组在单独的接口中。这可以提高可读性,在某些情况下,可以显式实现隐藏的成员。
internal interface IClassWithHiddenMembers {
Int32 Protected { get; }
}
class ClassWithHiddenMembers : IClassWithHiddenMembers {
protected Int32 Protected { get { return _protected; } }
Int32 IClassWithHiddenMembers.Protected {
get { return _protected; }
}
protected Int32 _protected;
}
“PropertyChanged”订阅者实现的示例还有另一个弱点。在“switch”运算符中比较的属性显示为字符串,这提供的信息非常少。如果有很多字符串名称需要比较,“switch”设计会变得无效并稍微降低性能。人们希望比较引用而不是字符串。因此,有时人们会想编写以下代码:
[Serializable]
public class NPC : INotifyPropertyChanged {
#region "INotifyPropertyChanged" interface implementation
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] String propertyName = "") {
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs evt) {
if (evt == null) return;
var handler = PropertyChanged;
if (handler != null) handler(this, evt);
}
#endregion "INotifyPropertyChanged" interface implementation
public const String DEFAULT_SOME_TEXT = "Default value";
public static readonly PropertyChangedEventArgs CHANGED_SOME_TEXT_EVENT_ARGS
= new PropertyChangedEventArgs(nameof(SomeText));
private String _someText = DEFAULT_SOME_TEXT;
[DefaultValue(DEFAULT_SOME_TEXT)]
public String SomeText {
get { return this._someText; }
set {
if (String.Equals(this._someText, value,
StringComparison.CurrentCultureIgnoreCase))
return;
this._someText = value;
OnPropertyChanged(CHANGED_SOME_TEXT_EVENT_ARGS);
}
}
...
public static void Npc_SomeTextPropertyChanged(
Object sender, PropertyChangedEventArgs e) {
if (e == NPC.CHANGED_SOME_TEXT_EVENT_ARGS) {
// Perform action
}}
令人惊讶的是,“PropertyChangedEventArgs”类以及其他从“EventArgs”继承的类没有重载的“Equals”方法。它应该看起来像以下代码:
public class PropertyChangedEventArgsEx: EventArgs {
public PropertyChangedEventArgsEx(string propertyName) {
this.PropertyName = propertyName; }
public virtual string PropertyName { get; private set; }
public override Boolean Equals(Object obj) {
return Object.ReferenceEquals(this, obj)
|| ((obj is PropertyChangedEventArgsEx)
&& String.Equals(PropertyName, ((PropertyChangedEventArgsEx)obj).PropertyName))
|| base.Equals(obj);
}
}
为什么这么经常不实现“Equals”方法?也许答案很简单。要记住这一点非常困难,特别是如果您不知道类将如何使用。此外,还需要实现“GetHashCode”和“ToString”方法,因为会出现警告“CS0659”。因此,人们希望利用AOP。
[Equals]
public class PropertyChangedEventArgsEx: EventArgs {
public PropertyChangedEventArgsEx(String propertyName)
{ this.PropertyName = propertyName; }
public virtual String PropertyName { get; private set; }
}
例如,有一个可扩展的工具“Fody”用于编织.NET程序集。它有一个好的“Equals”生成器“Fody/Equals”(“NuGet PM: Install-Package Equals.Fody”)。它为用“[Equals]”属性装饰的类生成“Equals”、“GetHashCode”和运算符方法。此外,工具“Fody/PropertyChanged”(“NuGet PM: Install-Package PropertyChanged.Fody”)提供了一种简单的方法在编译时将“INotifyPropertyChanged”代码注入属性。
示例 3:反射
反射用于在运行时获取程序集的元数据和类型结构。“System.Reflection”命名空间中包含提供对运行程序元数据访问的类。反射还允许在运行时创建新代码,然后动态执行它。有大量文章致力于此,例如:“.NET中的反射”、“反射优化技术”、“带示例的反射”、“动态类型使用Reflection.Emit”、“HyperDescriptor:加速动态属性访问”、“ExpressionToCode, Passert”、“运行时创建静态方法”、“反射慢还是快?实际演示”等等。
人们可能会认为,在添加“dynamic”关键字后,动态编程的改进就停止了。但事实并非如此。下一个“dynamic”步骤可能是“nameof”运算符。许多包含反射方法的源都包含查找或创建成员的字符串名称。在这种情况下,使用“nameof”运算符非常方便。让我们回顾一下“NPC”代码的示例。属性可能具有“System.ComponentModel.DefaultValueAttribute”描述的默认值。属性的值只能通过反射获得。
[DefaultValue(DEFAULT_SOME_TEXT)]
public String SomeText {
get { return this._someText; }
set {
if (String.IsNullOrEmpty(value)) {
value = GetType()
.GetProperty(nameof(SomeText))
.GetCustomAttribute().Value as String;
}
if (String.Equals(this._someText, value,
StringComparison.CurrentCultureIgnoreCase))
return;
this._someText = value;
OnPropertyChanged();
}}
上面的示例演示了“nameof”运算符在反射中的作用。然而,它也显示了反射的弱点。需要执行太多低效的操作才能获取所需属性。代码范围在编译时是已知的,但需要通过处理无数字符串参数和列表枚举来“动态”获取它。但是获取属性信息是反射中最常见的操作。它应该更简单、更高效。
再次查看“PropertyChanged”事件中的“PropertyChangedEventArgs”类后,可以清楚地理解,也许“PropertyName”应该命名为“Property”,类型为“System.Reflection.PropertyInfo”。该类型具有正确实现的“Equals”和“ToStrinsg”方法,并具有足够的信息来决定如何进行比较和转换为字符串。然而,出于某些原因,“System.Reflection”命名空间中的类不建议使用。似乎动态性很慢,并且会破坏面向对象原则。 因此,它应该单独保存,仅在很少的情况下使用。但在实际项目中,C#中反射的状况类似于C++中的“#define”预处理器指令:“C预处理器技巧、技巧和惯用法”或“宏有哪些技巧”。它只应在无法避免的地方使用。然而,在实际项目中,该指令被普遍使用。今天,原生编程和元编程(反射、DLR)在C#之间存在巨大的差距。也许,这种差距需要缩小。让我们想象一下如何做到这一点。
传递给其方法的反射元素的许多参数是字符串类型。执行字符串转换和动态绑定需要大量时间。然而,通常有必要引用当前或公共上下文(范围)中存在的现有代码元素或对其进行反射。 如果考虑到这一点,可以显著提高性能。让我们考虑以下代码。
namespace ReflectionPatterns {
public reflecting field pattern _field1; // Any field with name “_field1”
public reflecting field pattern Int32 _field2;
[RequireAttribute(typeof(SerializableAttribute))]
public reflecting field pattern Int32 _field3;
[TypeCast][AnyName][Static][MemberScope(Scope.Private | Scope.Internal)]
public reflecting field pattern Object PrivateOrInternalFieldWithAnyName;
[TypeCast][RegularExpression(@"^_field\d+$")]
public reflecting field pattern Object FieldWithRegExprName;
public reflecting property pattern Property1; // Any property with name “Property1”
public reflecting property pattern Int32 Property2 {get;} // Getter is required
public reflecting property pattern Int32 Property3 {set;} // Setter is required
[TypeCast][AnyName][field:RequireAttribute(typeof(SerializableAttribute))]
public reflecting property pattern Int32 PropertyWithAnyName {get; set;}
[RegularExpression(@"^Property\d+$")]
public reflecting property pattern PropertyWithRegExprName;
public reflecting event pattern Event1; // Any event with name “Event1”
public reflecting event pattern EventHandler Event2;
[TypeCast][AnyName][MemberScope(Scope.Public | Scope.Protected)]
public reflecting event pattern EventHandler PublicOrProtectedEventWithAnyName;
[TypeCast][RegularExpression(@"^.*?Event(?:\d+)?$")]
public reflecting event pattern EventWithRegExprName;
public reflecting method pattern Method; // Any input and output arguments of
// the method having name “Method”
public reflecting method pattern Int32 Method1; // Any input arguments of
// the method having name “Method1”
[return:RequireAttribute(typeof(OutAttribute))]
public reflecting method pattern Int32 Method2 where T:struct;
// The pattern having dynamic substituted parts
[DynamicType("MethodType")][DynamicName("MethodName")]
public reflecting method pattern Int32 MethodWithDynamicParts(
[RegularExpression(@"^arg\d*$")] [DynamicName("ArgumentName")] T arg);
[RegularExpression(@"^.*?Class(?:\d+)?$")]
[BaseClassName(@"^Base.*?Class$")][InterfaceName(@"^I.*?Connection$")]
[NestedPatern(Class1, Method1, Property1)][RequiredMembers(typeof(IInterface))]
public reflecting class pattern ClassRegExprName : IInterface1, Iinterface2;
// The class pattern may be used to define attribute patterns
}
反射模式定义可以像委托一样编写和放置。这些模式描述了静态和动态查找类及其成员的直观清晰的规则。这些规则是一种动态语言,如“LINQ”。它通过反射模式和一组扩展方法提供了本地元信息查询功能。这些模式让开发人员能够直观地理解需要查找的内容。 它还允许在编译时检查语法并简化搜索查询。在许多情况下,反射的结果可以在编译时获得。通过“reflect<T>(target)”运算符获取与反射模式对应的 P#。模板参数“T”是反射模式或被属性、嵌套到程序集或其他类型的类型。参数“target”是类实例、类型或字符串。
var fieldInfo1 = reflect<_field1>(this); // Exact match
var fieldInfos = reflect<PrivateOrInternalFieldWithAnyName[]>(typeof(SomeType));
var fieldInfo01 = reflect<_field1>(); // It calls for current scope
// and finds exact math
class SomeType {
public FieldInfo InstanceGetFieldInfo() {
var fieldInfo = reflect<_field1>();
// It's equivalent to
// FieldInfo fieldInfo = reflect<_field1>(GetType())
return fieldInfo;
}
public static FieldInfo StaticGetFieldInfo() {
var fieldInfo = reflect<_field1>();
// It's equivalent to
// FieldInfo fieldInfo = reflect<_field1>(typeof(SomeType))
return fieldInfo;
}
这种方法可以大大简化与属性的交互方式。
[DefaultValue(DEFAULT_SOME_TEXT)]
public String SomeText {
get { return this._someText; }
set {
if (String.IsNullOrEmpty(value)) {
value = reflect<DefaultValueAttribute>()?.Value as String;
}
if (String.Equals(this._someText, value,
StringComparison.CurrentCultureIgnoreCase))
return;
this._someText = value;
OnPropertyChanged();
}}
编译器可以以最佳方式简化上面显示的代码。让我们看其他示例。
// Find methods that match the pattern “MethodPattern”,
// where the class placed in current assembly matches the pattern “ClassPattern”
MethodInfo[] mi = reflect<MethodPattern[], ClassPattern>();
// Find methods that match the pattern “MethodPattern”,
// where the class placed in custom assembly matches the pattern “ClassPattern”
MethodInfo[] mi = reflect<MethodPattern[], ClassPattern>(
"MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7779fa1be111eb0c");
// Maybe the reflection should be more strongly typed,
// then increase productivity and reduce errors.
FieldInfo<FieldPattern> fieldInfo = reflect<FieldPattern>();
MethodInfo<MethodPattern> methodInfo = reflect<MethodPattern>();
MethodInfo<PropertyPattern> propertyInfo = reflect<PropertyPattern>();
// Errors can be detected at compile time. The boxing/unboxing doesn't apply.
OldValue = propertyInfo.GetValue(this); propertyInfo.SetValue(this, NewValue);
methodInfo.Invoke(this, arg0, arg1, ..., argN);
// Using dynamically substituted parts (see example above)
MethodInfo<MethodWithDynamicParts> methodInfo = reflect<MethodWithDynamicParts>(
"Namespace.Class", new {MethodType = typeof(Int32), ArgumentName="argument0"});
);
通过反射获取和设置属性值以及调用方法的示例表明,还需要进一步的功能。这就是“类型化引用”。
类型化引用由“reference<T>(target)”运算符定义。它类似于“reflect”运算符。该运算符允许链接到属性、事件、方法或字段。其实现应该是轻量级的,并类似于C++中的“引用”。其性能应该接近于直接调用由其引用的代码部分。引用的行为应该类似于它们引用的成员。引用的赋值和相等性可以定义为附加操作。在这种情况下,通过比较类型化引用而不是字符串,NPC订阅者的性能可以显著提高。
namespace ReflectedTypes {
[AnyName]
public reflecting property pattern PropertyPattern {get;}
public reference PropertyRef : PropertyPattern; // It's like delegate definition
// A warning “It's better not to refer to the field” is accompanied
// a reference to field.
class NPC : INotifyPropertyChanged {
...
public Int32 Property {
get { return _property; }
set {
if (_property == value) return;
_property = property;
PropertyChanged(reference<PropertyRef>(this));
// also reference<PropertyRef>(), reflect<PropertyPattern>(this)
}
}
private Int32 _property;
}
class Program {
static void NPCSubscriber(Object sender, PropertyChangedEventArgs e) {
PropertyRef propRef = e.Property as PropertyRef;
if (propRef != null && propRef is reference(NPC.Property)) {
Console.WriteLine("Type: {0}, name: {0}, value: {1}",
propRef.Container.GetType(), propRef, propRef.Reference);
// “Container” is instance or Type in case of reference to static member.
// “Reference” is auto-generated property, event or method wrapper.
}
}
static void Main(string[] args) {
var obj = new NPC();
obj.PropertyChanged += NPCSubscriber;
}
}}
乍一看,由于已经存在委托,因此不需要引用。然而,在许多情况下,除了事件之外,不需要其繁重的功能。委托的所有功能(参见“Delegate”和“MulticastDelegate”类的源代码)都可以通过普通类中的类型化引用透明地编写,而无需任何 hack。
public abstract class Delegate : ICloneable, Iserializable {
// _target is the object we will invoke on
[System.Security.SecurityCritical]
internal Object _target;
// MethodBase, either cached after first request or assigned from a DynamicMethod
// For open delegates to collectible types, this may be a LoaderAllocator object
[System.Security.SecurityCritical]
internal Object _methodBase;
// _methodPtr is a pointer to the method we will invoke
// It could be a small thunk if this is a static or UM call
[System.Security.SecurityCritical]
internal IntPtr _methodPtr;
// In the case of a static method passed to a delegate, this field stores
// whatever _methodPtr would have stored: and _methodPtr points to a
// small thunk which removes the "this" pointer before going on
// to _methodPtrAux.
[System.Security.SecurityCritical]
internal IntPtr _methodPtrAux;
// This constructor is called from the class generated by the
// compiler generated code
[System.Security.SecuritySafeCritical] // auto-generated
protected Delegate(Object target, String method) {
if (target == null)
throw new ArgumentNullException("target");
if (method == null)
throw new ArgumentNullException("method");
Contract.EndContractBlock();
if (!BindToMethodName(target, (RuntimeType)target.GetType(), method,
DelegateBindingFlags.InstanceMethodOnly |
DelegateBindingFlags.ClosedDelegateOnly))
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth"));
}
...
[System.Security.SecurityCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern bool BindToMethodName(Object target,
RuntimeType methodType, String method, DelegateBindingFlags flags);
...
[System.Security.SecuritySafeCritical] // auto-generated
protected virtual object DynamicInvokeImpl(object[] args) {
RuntimeMethodHandleInternal method =
new RuntimeMethodHandleInternal(GetInvokeMethod());
RuntimeMethodInfo invoke =
RuntimeType.GetMethodBase((RuntimeType)this.GetType(), method);
return invoke.UnsafeInvoke(this, BindingFlags.Default, null, args, null);
}
...
[System.Security.SecuritySafeCritical] // auto-generated
protected virtual MethodInfo GetMethodImpl() {
if ((_methodBase == null) || !(_methodBase is MethodInfo)) {
IRuntimeMethodInfo method = FindMethodHandle();
RuntimeType declaringType = RuntimeMethodHandle.GetDeclaringType(method);
if(RuntimeTypeHandle.IsGenericTypeDefinition(declaringType)
|| RuntimeTypeHandle.HasInstantiation(declaringType)) {
bool isStatic = (RuntimeMethodHandle.GetAttributes(method)
& MethodAttributes.Static) != (MethodAttributes)0;
if (!isStatic) {
if (_methodPtrAux == (IntPtr)0) {
Type currentType = _target.GetType();
Type targetType = declaringType.GetGenericTypeDefinition();
while (currentType != null) {
if (currentType.IsGenericType
&& currentType.GetGenericTypeDefinition() == targetType) {
declaringType = currentType as RuntimeType;
break;
}
currentType = currentType.BaseType;
}
BCLDebug.Assert(currentType != null || _target.GetType().IsCOMObject,
"The class hierarchy should declare the method");
} else {
MethodInfo invoke = this.GetType().GetMethod("Invoke");
declaringType = (RuntimeType)invoke.GetParameters()[0].ParameterType;
}
}
}
_methodBase = (MethodInfo)RuntimeType.GetMethodBase(declaringType, method);
}
return (MethodInfo)_methodBase;
}
...
}
通过研究代码,可以得出结论,没有引用就无法编写高质量的代码。这就是为什么C#的开发人员通过一些小技巧来创建它(参见具有内部可访问性的“_methodPtr”或“_methodPtrAux”字段)。如果没有引用,就必须使用缓慢的反射。在“System.Reflection”和“System.Dynamic”命名空间中的类也存在类似的情况。那么也许应该显式添加类型化引用,而无需任何技巧?
建议使用“reflect”和“reference”运算符进行反射。也许这比现有方法更好。在大多数情况下,“.NET”会执行相当好的优化,因为可以在编译时预测需要什么类型的信息。编译器可以根据情况创建各种代码。另外,还应该注意的是,通过使用它们,反射对开发人员来说变得更加友好。
结论
我相信“nameof”运算符提供了新的良好机会,完全值得关注。它将为我们节省数不清的时间来修复错误的重构、关于编码风格和代码审查的争论,以及对隐式错误的抓耳挠腮。另一方面,它揭示了一些语言问题,这些问题应该得到妥善研究并在 C# 中进行改进。还有其他非常有趣的问题将在未来的文章中讨论。
我将非常感谢读者的意见,他们表达自己的观点并回答问题。您是否有任何实际的反射(“System.Reflection”、“System.Reflection.Emit”、“System.Dynamic”、“表达式树”、“dynamic”等)在实际项目中使用过的良好示例?如果可以在您的示例中使用“nameof”运算符,那就太好了。
关注点
发现和学习良好的软件工程原理、源代码中的优秀方法以及现代框架使我们能够编写真正灵活的代码,这真是太棒了。
历史
2016年2月9日 - 首次发布