表达式与 nameof

Expression vs nameof

使用 nameof 而不是 expressions 来提取 属性 个名字是个好主意吗?

//method with expression
protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression, bool isValid, [param: Localizable(true)] string validationError)
{
    string propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
    RaisePropertyChanged(propertyName, isValid, validationError);
}

//the same logic without expression
protected void RaisePropertyChanged(string propertyName, [param: Localizable(true)] string validationError)
{
    RaisePropertyChanged(propertyName, validationError == null, validationError);
}

但叫法不同

public string Url
{
    set
    {
        //with nameof
        RaisePropertyChanged(nameof(Url), this.GetValidationError());
        //with expression
        RaisePropertyChanged(() => Url, this.GetValidationError());
    }
}

您知道每种方法的优缺点是什么?或者只有执行速度很重要?我的意思是 nameof 将在编译后替换为 const 值。

为什么要使用表达式来做一些你可以在编译时做的事情? nameof(Url) 在编译时确定。它花费 0 毫秒。事后评价。当您可以使用 nameof.

时,构建表达式既昂贵又毫无意义

所以底线是:不要使用表达式,除非你真的必须这样做(你已经在表达式中工作,你必须从那里开始)。否则使用 nameof.

总结...

...使用nameof...

...当您只想要 属性 名称时。

...或使用表达式树...

...当您需要时:

  • ...内省所选成员。
  • ...得到谁声明了整个成员。
  • ...获取声明整个成员的命名空间。
  • ...等等。

由于表达式树只是一种数据结构,除非您将其编译成委托,否则它将所选成员公开为 MemberInfo(即 MethodInfoPropertyInfo... ) 使您能够进行 反射.

除非您需要,否则请使用 nameof

nameof() 对比 Expression

如果您只需要 属性 的名称作为字符串,那么您可以使用 nameof()。但是,如果您的目标是为某些对象获取 属性 的值,则使用表达式(详见下文)。

作为 属性 selector 的表达式在泛型方法中使用,以便 select 来自对象 T 的 属性。正如在 IQueryable 的 Select 声明中一样。在那种情况下使用 nameof() 没有任何意义,因为表达式可以安全地编译 属性 selected 是对象 T.Then 表达式的一部分然后由数据库提供者或 Enumerable(又名 Linq)使用来提供结果。

至于 RaisePropertyChanged,我认为 Expression 的重载在 C# 6 之前很有用(当 nameof() 被引入时)并且它仍然存在可能是为了向后兼容。在这种情况下使用 nameof() 会快很多。

阐明 Expression 胜过 nameof() 的示例:

(注意,Func是为了简单起见,思路在如何调用方法上)

public static class MyExtensions
{
    public static IEnumerable<K> MySelect<T, K>(this IEnumerable<T> source, 
                                                                      Func<T, K> selector)
    {
        foreach(T item in source)
        {
            yield return selector(item);
        }
    }

    public static IEnumerable<K> MySelect2<T, K>(this IEnumerable<T> source, 
                                                                     string propertyName)
    {

        foreach (T item in source)
        {
            // K value = GET VALUE BY REFLECTION HERE THROUGH T AND PROPERTY NAME;
            yield return value;
        }
    }
}

用法:

 // Fancy Intellisense when typing x. with compile time type inference
IEnumerable<int> results = arr.MySelect(x => x.Length);

// No so pretty, you need to explictly specify the type and you can pass whatever string
IEnumerable<int> results2 = arr.MySelect2<string, int>(nameof(string.Length));

虽然我们遇到了麻烦:

// Doesn't Compile, No Property1 found in x.
IEnumerable<int> results = arr.MySelect(x => x.Property1);

// Compiles and throw a run-time error as there is no Message property in string.
IEnumerable<int> results2 = arr.MySelect2<string, int>(nameof(Exception.Message));

我建议将字符串与 [CallerMemberName] 属性结合使用。

protected void RaisePropertyChanged([param: Localizable(true)] string validationError, [CallerMemberName]string propertyName = null)
{
    // whatever
}

您必须将 属性Name 放在参数的末尾,因为默认值必须是 null/string.Empty.

编译器会将调用者未提供 属性Name 的所有调用替换为您所在的当前 属性 的字符串:

public string SomeString
{
  get { return _someString}
  set 
  { 
    _someString = value;
    OnPropertyChanged("your validation error");
  }
}

将自动转换为:

public string SomeString
{
  get { return _someString}
  set 
  { 
    _someString = value;
    OnPropertyChanged("your validation error", "SomeString");
  }
}

编译器!

如果你不想在 属性 之外使用它,你可以用 nameof(SomeString) 调用它。

我会在 属性Selector 上推荐它,因为它是编译时间,并且不会在运行时花费你 cpu 个周期。除了直接用字符串调用它之外,它还重构了保存。

当您确实需要有关调用者的更多信息时,请使用 属性带有表达式树的选择器。但是当你可以在运行时做一些事情时,就不需要使用cpu-cycles。

例如,Microsoft 模式和实践团队在 Prism 中的 OnPropertyChanged 实现如下所示:

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
  // ISSUE: reference to a compiler-generated field
  PropertyChangedEventHandler changedEventHandler = this.PropertyChanged;
  if (changedEventHandler == null)
    return;
  PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
  changedEventHandler((object) this, e);
}

但是还有属性表达式版本:

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
  this.OnPropertyChanged(PropertySupport.ExtractPropertyName<T>(propertyExpression));
}

这只会做更多的工作,因为它需要通过反射提取名称(性能影响),然后使用字符串作为参数调用原始实现。

这就是为什么 nameof() and/or CallerMemberName 更可取。