【问题标题】:Expression vs nameof表达式与名称
【发布时间】:2016-07-13 06:04:05
【问题描述】:

使用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 通常可以满足您的需求,而表达式可以用于动态和复杂的查询。

标签: c# .net expression c#-6.0 nameof


【解决方案1】:

为什么要使用表达式来做一些你可以在编译时做的事情? nameof(Url) 在编译时确定。它花费0毫秒。之后进行评估。当您可以使用 nameof 时,构建表达式既昂贵又毫无意义。

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

【讨论】:

  • 只是一个小补充:如果你不小心使用表达式可能会导致运行时异常,而nameof 会给你编译时错误。
【解决方案2】:

总结...

...使用nameof...

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

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

...当您需要时:

  • ...内省选定的成员。
  • ...获取谁声明了整个成员。
  • ...获取声明整个成员的名称空间。
  • ...等等。

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

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

【讨论】:

    【解决方案3】:

    nameof()Expression

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

    表达式作为属性选择器用于泛型方法中,以便从对象T 中选择属性。就像IQueryable 的 Select 语句一样。在这种情况下,我没有任何意义使用nameof(),因为表达式可以使其安全地编译所选属性是对象 T 的一部分。然后表达式由 db 提供程序或 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));
    

    【讨论】:

    • Keep in maind OnPropertyChanged 可以与CallerMemberName 属性一起使用,当只需要调用者姓名时,imo 优于上述两种方式。来自模式和实践团队的 Prism 中的 Microsoft 在他们的 ViewModelBase 中实现了 propertyselector 和 CallerMemberName。我认为这并不重要(除了 callerMemberName 和 nameof 是编译时,表达式是运行时,这是性能损失),这完全取决于偏好。
    【解决方案4】:

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

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

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

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

    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) 调用它。

    我会推荐它而不是 propertySelector,因为它是编译时间,并且在运行时不会花费你的 cpu 周期。除了直接用字符串调用之外,它还在重构 save 。

    当您确实需要有关调用者的更多信息时,请使用带有表达式树的 propertySelector。但是当你可以在运行时做某事时,就不需要使用 cpu-cycles 了。

    例如,Microsoft Pattern and Practices 团队在 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);
    }
    

    但也有propertyExpression版本:

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

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

    这就是为什么nameof() 和/或CallerMemberName 更可取的原因。

    【讨论】:

      猜你喜欢
      • 2020-02-02
      • 1970-01-01
      • 2011-05-11
      • 2019-08-09
      • 2010-09-21
      • 2023-04-09
      • 1970-01-01
      相关资源
      最近更新 更多