首页 文章

为什么我要避免在C#中使用Properties?

提问于
浏览
99

在他出色的着作“CLR Via C#”中,杰弗里里希特说他不喜欢属性,并建议不要使用它们 . 他给出了一些理由,但我并不理解 . 任何人都可以向我解释为什么我应该或不应该使用属性?在C#3.0中,具有自动属性,这会改变吗?

As a reference, I added Jeffrey Richter's opinions:

• property 可以是只读的或只写的;字段访问始终是可读写的 . 如果定义属性,最好同时提供get和set访问器方法 .

•属性方法可能会抛出异常;字段访问永远不会抛出异常 .

•属性不能作为out或ref参数传递给方法;一个领域可以 . 例如,以下代码将无法编译:

using System;
public sealed class SomeType
{
   private static String Name 
   {
     get { return null; }
     set {}
   }
   static void MethodWithOutParam(out String n) { n = null; }
   public static void Main()
   {
      // For the line of code below, the C# compiler emits the following:
      // error CS0206: A property or indexer may not
      // be passed as an out or ref parameter
      MethodWithOutParam(out Name);
   }
}

•属性方法可能需要很长时间才能执行;现场访问总是立即完成 . 使用属性的一个常见原因是执行线程同步,这可以永久停止线程,因此,如果需要线程同步,则不应使用属性 . 在那种情况下,一种方法是优选的 . 此外,如果可以远程访问您的类(例如,您的类派生自System.MashalByRefObject),则调用property方法将非常慢,因此,方法优先于属性 . 在我看来,派生自MarshalByRefObject的类永远不应该使用属性 .

•如果连续多次调用,则属性方法每次都可能返回不同的值;每次返回相同的值 . System.DateTime类具有readonly Now属性,该属性返回当前日期和时间 . 每次查询此属性时,它将返回不同的值 . 这是一个错误,Microsoft希望他们可以通过将Now设为方法而不是属性来修复该类 .

•属性方法可能会导致可观察到的副作用;现场访问永远不会 . 换句话说,类型的用户应该能够以他或她选择的任何顺序设置由类型定义的各种属性,而不会注意到类型中的任何不同行为 .

•属性方法可能需要额外的内存或返回对实际上不属于对象状态的内容的引用,因此修改返回的对象对原始对象没有影响;查询字段始终返回对保证属于原始对象状态的对象的引用 . 使用返回副本的属性可能会让开发人员感到非常困惑,而且这种特性通常没有记录 .

14 回答

  • 17

    不喜欢属性的Jeff之所以是因为它们看起来像是字段 - 所以不了解差异的开发人员会把它们看作是字段,假设它们执行起来便宜等等 .

    就个人而言,我在这一点上不同意他 - 我发现属性使客户端代码比等效方法调用更容易阅读 . 我同意开发人员需要知道属性基本上是伪装的方法 - 但我认为教育开发人员比使用方法更难以阅读代码更好 . (特别是,看到Java代码在同一个语句中调用了几个getter和setter,我知道等效的C#代码会更容易理解.Demeter法在理论上非常好,但有时 foo.Name.Length 真的是正确的使用...)

    (不,自动实现的属性并没有真正改变这一点 . )

    这有点像反对使用扩展方法的论点 - 我可以理解推理,但实际的好处(当谨慎使用时)超过了我认为的缺点 .

  • 3

    好吧,让我们一一拿走他的论点:

    property 可以是只读的或只写的;字段访问始终是可读写的 .

    这是 properties 的胜利,因为您对访问有更细粒度的控制 .

    属性方法可能会抛出异常;字段访问永远不会抛出异常 .

    虽然这大部分都是正确的,但您可以在未初始化的对象字段上调用方法,并抛出异常 .

    •属性不能作为out或ref参数传递给方法;一个领域可以 .

    公平 .

    •属性方法可能需要很长时间才能执行;现场访问总是立即完成 .

    它也可能需要很短的时间 .

    •如果连续多次调用,则属性方法每次都可能返回不同的值;每次返回相同的值 .

    不对 . 你怎么知道该字段的值没有改变(可能是另一个线程)?

    System.DateTime类具有readonly Now属性,该属性返回当前日期和时间 . 每次查询此属性时,它将返回不同的值 . 这是一个错误,Microsoft希望他们可以通过将Now设为方法而不是属性来修复该类 .

    如果这是一个错误,那就是一个小问题 .

    •属性方法可能会导致可观察到的副作用;现场访问永远不会 . 换句话说,类型的用户应该能够设置由他或她选择的任何顺序的类型定义的各种属性,而不会注意到类型中的任何不同行为 .

    公平 .

    •属性方法可能需要额外的内存或返回对实际上不属于对象状态的内容的引用,因此修改返回的对象对原始对象没有影响;查询字段始终返回对保证属于原始对象状态的对象的引用 . 使用返回副本的属性可能会让开发人员感到非常困惑,而且这种特性通常没有记录 .

    对于Java的getter和setter来说,大多数的抗议都可以说 - 而且我们在实践中没有这些问题已经有很长一段时间了 .

    我认为大多数问题都可以通过更好的语法突出显示来解决(即区别属性),这样程序员就知道会发生什么 .

  • 5

    我没有看过这本书,你没有引用它你不理解的部分,所以我不得不猜 .

    有些人不喜欢属性,因为它们可以使你的代码做出令人惊讶的事情 .

    如果我输入 Foo.Bar ,阅读它的人通常会希望这只是访问Foo类的成员字段 . 它确定性的.660443 . 我可以一遍又一遍地调用它,每次都得到相同的结果 .

    相反,对于属性,它实际上可能是函数调用 . 它可能是一个无限循环 . 它可能会打开数据库连接 . 每次访问它时,它可能会返回不同的值 .

    这与Linus讨厌C的原因类似 . 您的代码可能令读者感到惊讶 . 他讨厌操作员重载: a + b 并不一定意味着简单的添加 . 这可能意味着一些非常复杂的操作,就像C#属性一样 . 它可能有副作用 . 它可以做任何事情 .

    老实说,我认为这是一个微弱的论点 . 这两种语言都充满了这样的东西 . (我们是否应该避免C#中的运算符重载?毕竟,可以在那里使用相同的参数)

    属性允许抽象 . 我们可以假装某个东西是一个常规字段,并将它当作一个字体使用,而不必担心幕后发生的事情 .

    这通常被认为是一件好事,但它显然依赖程序员编写有意义的抽象 . 您的属性应该像字段一样 . 他们不应该执行昂贵或不安全的操作 . 我们希望能够将它们视为字段 .

    但是,我有另一个理由发现它们不够完美 . 它们不能通过引用传递给其他函数 .

    字段可以作为 ref 传递,允许被调用的函数直接访问它 . 函数可以作为委托传递,允许被调用的函数直接访问它 .

    属性......不能 .

    太糟糕了 .

    但这并不意味着属性是邪恶的或不应该被使用 . 出于许多目的,它们很棒 .

  • 4

    早在2009年,这个建议似乎只是对660447_品种的收获 . 今天,它几乎可笑地过时了 .

    一个非常重要的一点是,很多答案似乎都在蠢蠢欲动,但并没有完全解决这些问题,这些声称的"dangers"属性是 an intentional part of the framework design!

    是的,属性可以:

    • 为getter和setter指定不同的访问修饰符 . 这是一个优于领域的优势 . 一个常见的模式是拥有一个公共getter和一个受保护的或内部的setter,这是一种非常有用的继承技术,仅靠字段无法实现 .

    • 抛出异常 . 到目前为止,这仍然是最有效的验证方法之一,尤其是在使用涉及数据绑定概念的UI框架时 . 在使用字段时,确保对象保持有效状态要困难得多 .

    • 需要很长时间才能执行 . 这里的有效比较是采用同样长的方法 - 而不是字段 . 除了一位作者的个人偏好之外,没有给出声明"a method is preferred"的依据 .

    • 在后续执行时从其getter返回不同的值 . 这几乎看起来像是一个笑话,非常接近于赞美 ref / out 参数的优点与字段,其中 ref / out 调用之后的字段值几乎保证与之前的值不同,并且不可预测地如此 .

    如果我们fairly well understood似乎回想起任何使用 DateTime.Now 的人的例子,并期望每次出现相同的值 . 至少在任何情况下,他们都不会把它搞砸了,就像假想的那样糟糕.660459_ .

    • 引起可观察到的副作用 - 这当然正是属性首先被发明为语言特征的原因 . 微软自己的Property Design指南表明,制定者的顺序无关紧要,否则就意味着temporal coupling . 当然,你只能因为在执行某些方法之前不能单独使用字段导致任何有意义的行为 .

    属性访问器实际上可以通过在执行任何操作之前强制对象进入有效状态来帮助防止某些类型的时间耦合 - 例如,如果类具有 StartDateEndDate ,则在 StartDate 之前设置 EndDate 可能会强制 StartDate 返回好 . 即使在多线程或异步环境中也是如此,包括事件驱动用户界面的明显示例 .

    属性可以执行的其他事项哪些字段不能包括:

    • Lazy loading,防止初始化顺序错误的最有效方法之一 .

    • Change Notifications,几乎是MVVM架构的完整基础 .

    • Inheritance,例如定义一个抽象 TypeName ,因此派生类可以提供有趣但但仍然是关于自身的恒定元数据 .

    • Interception,多亏了上述内容 .

    • Indexers,每个曾经不得不与COM互操作的人以及 Item(i) 呼叫的不可避免的呕吐都将被认为是一件美妙的事情 .

    • 使用PropertyDescriptor,这对于创建设计器和XAML框架至关重要 .

    里希特显然是一位多产的作家,对CLR和C#了解很多,但我不得不说,看起来好像他最初写这篇建议时(我不确定它是否在他最近的修订中 - 我真诚地希望不是)他只是不想放弃旧的习惯,而且很难接受C#的惯例(例如对C) .

    我的意思是,他的"properties considered harmful"论证基本上归结为一个语句:属性看起来像字段,但它们可能不像字段那样 . 声明的问题是, it isn't true ,或者充其量是极具误导性的 . 属性看起来不像字段 - 至少,它们不应该看起来像字段 .

    C#中有两个非常强大的编码约定,其他CLR语言共享相似的约定,如果你不遵循它们,FXCop会尖叫你:

    • 字段应始终是私有的,永远不公开 .

    • 字段应在camelCase中声明 . 属性是PascalCase .

    因此, Foo.Bar = 42 是属性访问器还是字段访问器没有歧义 . 它's a property accessor and should be treated like any other method - it might be slow, it might throw an exception, etc. That'的性质Abstraction - 这完全取决于宣告类如何反应的自由裁量权 . class 设计师应该应用最少惊喜的原则,但是呼叫者不应该对某个属性做任何假设,除非它按照它在锡上所说的那样做 . That's on purpose.

    属性的替代方法是getter / setter方法 . 那's the Java approach, and it'是controversial since the beginning . 它是's fine if that'你的包,但它不是我们如何在.NET阵营中滚动 . 我们尝试,至少在静态类型系统的范围内,以避免福勒称之为Syntactic Noise . 我们不需要额外的括号,额外的 get / set 疣或额外的方法签名 - 如果我们能够避免它们而不会失去清晰度 .

    说出你喜欢的任何东西,但 foo.Bar.Baz = quux.Answers[42] 总是比 foo.getBar().setBaz(quux.getAnswers().getItem(42)) 更容易阅读 . 当你每天阅读数千行时,它会有所不同 .

    (如果你对上段的自然反应是说,“确定它很难读,但如果你把它分成多行会更容易”,那么我很遗憾地说你完全错过了这一点 . )

  • 0

    我没有看到为什么你不应该使用一般属性的任何原因 .

    C#3中的自动属性只能简化语法(一种合成糖) .

  • 171

    这只是一个人的意见 . 我读了很多c#书,我还没有看到其他人说“不要使用属性” .

    我个人认为属性是关于c#的最好的东西之一 . 它们允许您通过任何您喜欢的机制来暴露状态 . 您可以在第一次使用某些东西时懒惰地实例化,并且可以在设置值等时进行验证 . 在使用和编写它们时,我只想将属性设置为setter和getters,这是一种更好的语法 .

    至于具有属性的警告,有一对 . 一个可能是滥用 property ,另一个可能是微妙的 .

    首先,属性是方法的类型 . 如果将复杂的逻辑放在属性中会很奇怪,因为类的大多数用户都希望该属性相当轻量级 .

    例如 .

    public class WorkerUsingMethod
    {
       // Explicitly obvious that calculation is being done here
       public int CalculateResult()
       { 
          return ExpensiveLongRunningCalculation();
       }
    }
    
    public class WorkerUsingProperty
    {
       // Not at all obvious.  Looks like it may just be returning a cached result.
       public int Result
       {
           get { return ExpensiveLongRunningCalculation(); }
       }
    }
    

    我发现使用这些案例的方法有助于区分 .

    其次,更重要的是,如果在调试时对属性进行评估,属性可能会产生副作用 .

    假设您有这样的属性:

    public int Result 
    { 
       get 
       { 
           m_numberQueries++; 
           return m_result; 
       } 
    }
    

    现在假设您在进行太多查询时发生异常 . 猜猜在调试器中开始调试和翻转属性时会发生什么?坏事 . 避免这样做!查看该属性会更改该程序的状态 .

    这些是我唯一的警告 . 我认为 properties 的好处远大于问题 .

  • 17

    这个理由必须在非常具体的背景下给出 . 它通常是相反的 - 它建议使用属性,因为它们为您提供了一个抽象级别,使您能够在不影响其客户端的情况下更改类的行为...

  • 6

    我不禁挑选杰弗里里希特的意见细节:

    property 可以是只读的或只写的;字段访问始终是可读写的 .

    错误:字段可以标记为只读,因此只有对象的构造函数可以写入它们 .

    属性方法可能会抛出异常;字段访问永远不会抛出异常 .

    错误:类的实现可以将字段的访问修饰符从公共更改为私有 . 尝试在运行时读取私有字段将始终导致异常 .

  • 4

    我不同意杰弗里里希特,但我可以猜到他为什么不喜欢属性(我还没看过他的书) .

    即使属性就像方法(实现方式),作为类的用户,我希望它的属性像公共字段一样“或多或少”,例如:

    • 在属性getter / setter中没有耗时的操作

    • 属性getter没有副作用(多次调用,不会改变结果)

    不幸的是,我已经看到了不那么表现的属性 . 但问题不在于 properties 本身,而是实施它们的人 . 所以它只需要一些教育 .

  • 11

    有一段时间我认为不使用属性,那就是写.Net Compact Framework代码 . CF JIT编译器不执行与桌面JIT编译器相同的优化,也不优化简单属性访问器,因此在这种情况下,添加一个简单属性会导致使用公共字段时出现少量代码膨胀 . 通常这不会是一个问题,但几乎总是在Compact Framework世界中你面临严格的内存限制,所以即使像这样的小额节省也很重要 .

  • 6

    您不应该避免使用它们,但出于其他贡献者给出的原因,您应该使用它们并具有资格和关注 .

    我曾经看过一个名为Customers的属性,它在内部打开一个进程外调用数据库并读取客户列表 . 客户端代码有一个'for(int i to Customers.Count)',它导致在每次迭代时单独调用数据库并访问所选客户 . 这是一个令人震惊的例子,它证明了保持 property 非常轻的原则 - 很少比内部现场访问更多 .

    使用属性的一个参数是它们允许您验证正在设置的值 . 另一个是属性的值可以是派生值,而不是单个字段,如TotalValue = amount * quantity .

  • 9

    我个人在创建简单的get / set方法时只使用属性 . 在涉及复杂的数据结构时,我会偏离它 .

  • 3

    该论点假设属性很糟糕,因为它们看起来像字段,但可以做出令人惊讶的操作 . 这种假设因.NET程序员的期望而无效:

    属性看起来不像字段 . 字段看起来像属性 .

    •属性方法可能会抛出异常;字段访问永远不会抛出异常 .

    因此,字段就像一个保证永不抛出异常的属性 .

    •属性不能作为out或ref参数传递给方法;一个领域可以 .

    所以,一个字段就像一个属性,但它有其他功能:传递给 ref / out 接受方法 .

    •属性方法可能需要很长时间才能执行;现场访问总是立即完成 . [...]

    所以,一个领域就像一个快速的 property .

    •如果连续多次调用,则属性方法每次都可能返回不同的值;每次返回相同的值 . System.DateTime类具有readonly Now属性,该属性返回当前日期和时间 .

    因此,除非将字段设置为不同的值,否则字段就像保证返回相同值的属性 .

    •属性方法可能会导致可观察到的副作用;现场访问永远不会 .

    同样,字段是保证不这样做的属性 .

    •属性方法可能需要额外的内存或返回对实际上不属于对象状态的内容的引用,因此修改返回的对象对原始对象没有影响;查询字段始终返回对保证属于原始对象状态的对象的引用 . 使用返回副本的属性可能会让开发人员感到非常困惑,而且这种特性通常没有记录 .

    这可能是令人惊讶的,但不是因为它是一个这样做的属性 . 相反,它几乎没有人在属性中返回可变副本,因此0.1%的情况令人惊讶 .

  • 35

    调用方法而不是属性会大大降低调用代码的可读性 . 例如,在J#中,使用ADO.NET是一场噩梦,因为Java不支持属性和索引器(它们本质上是属性)带参数) . 生成的代码非常难看,空的括号方法调用遍布整个地方 .

    对属性和索引器的支持是C#over Java的基本优势之一 .

相关问题