65.9K
CodeProject 正在变化。 阅读更多。
Home

谈论“nameof”运算符。

2016年2月8日

CPOL

11分钟阅读

viewsIcon

26204

downloadIcon

231

本文将讨论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”(CodePlexVS 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日 - 首次发布

© . All rights reserved.