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

时间:2009-03-29 14:36:24

标签: c# properties

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

作为参考,我添加了Jeffrey Richter的观点:

•财产可以是只读或只写;字段访问始终是可读写的。 如果定义属性,最好同时提供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), 调用属性方法将非常慢,因此,一个方法比a更受欢迎 属性。在我看来,派生自MarshalByRefObject的类永远不应该使用 属性。

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

•属性方法可能会导致可观察到的副作用;现场访问永远不会。其他 单词,类型的用户应该能够设置由any中的类型定义的各种属性 命令他或她选择时不注意类型中的任何不同行为。

•属性方法可能需要额外的内存或返回对某些内容的引用 这实际上不是对象状态的一部分,因此修改返回的对象没有 对原始物体的影响;查询字段始终返回对该对象的引用 保证是原始对象状态的一部分。使用该属性 返回副本可能会让开发人员非常困惑,而且这种特性经常出现 没有记录。

14 个答案:

答案 0 :(得分:172)

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

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

(不,自动实现的属性并没有真正改变任何一个。)

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

答案 1 :(得分:35)

好吧,让我们逐一论证:

  

财产可以是只读的或   只写;现场访问始终是   可读和可写。

这是物业的胜利,因为您对访问有更细粒度的控制。

  

属性方法可能抛出一个   例外;现场访问永远不会抛出   例外。

虽然这几乎是正确的,但您可以在未初始化的对象字段上调用方法,并抛出异常。

  

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

展。

  

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

这也可能需要很短的时间。

  

•如果连续多次调用,a   属性方法可能返回不同的   每次都有价值;一个字段返回   每次都有相同的价值。

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

  

System.DateTime类有一个   readonly现在返回的属性   当前日期和时间。每次你   查询此属性,它将返回一个   不同的价值。这是个错误,   微软希望他们能够   通过使Now成为方法来修复类   而不是财产。

如果这是一个错误,那就是一个小错误。

  

•属性方法可能会导致   可观察到的副作用;现场访问   从来不会。换句话说,是一个用户   一个类型应该能够设置各种   由任何类型定义的属性   命令他或她选择没有   注意到任何不同的行为   类型。

展。

  

•属性方法可能需要   额外的记忆或返回一个   引用不是的东西   实际上是对象状态的一部分,   所以修改返回的对象了   对原始物体没有影响;   查询字段总是返回一个   引用一个对象   保证是原件的一部分   对象的状态。使用a   返回副本的属性可以是   这对开发人员来说非常困惑   特征往往不是   记录。

大多数抗议活动都可以说是针对Java的吸气者和制定者 - 而且我们在实践中没有这些问题已经有了很长一段时间。

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

答案 2 :(得分:18)

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

有些人不喜欢属性,因为它们会让你的代码做出令人惊讶的事情。

如果我输入Foo.Bar,那么阅读它的人通常会希望这只是访问Foo类的成员字段。这是一种廉价,几乎免费的操作,而且是确定性的。我可以一遍又一遍地调用它,每次都得到相同的结果。

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

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

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

属性允许抽象。我们可以假装某些东西是常规字段,并将其视为一个字段,而不必担心幕后发生的事情。

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

然而,我还有另一个理由认为它们不够完美。它们不能通过引用传递给其他函数。

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

属性......不能。

太糟糕了。

但这并不意味着财产是邪恶的或不应该使用。出于许多目的,它们很棒。

答案 3 :(得分:17)

早在2009年,这个建议似乎只是Who Moved My Cheese种类的收获。今天,它几乎可笑地过时了。

一个非常重要的一点是,很多答案似乎都在悄悄地展开,但并没有完全解决的问题是这些声称的危险"属性是框架设计的有意识部分!

是的,属性可以:

  • 为getter和setter指定不同的访问修饰符。这是字段上的优势。一个常见的模式是拥有一个公共getter和一个 protected internal setter,这是一种非常有用的继承技术,仅靠字段无法实现。

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

  • 需要很长时间才能执行。这里的有效比较是使用方法,这需要同样长 - 而不是字段。声明没有给出基础"首选方法"除了一位作者的个人偏好外。

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

    如果我们在谈论没有传入耦合的单线程访问的具体(和实际学术)情况,它fairly well understood它只是糟糕的属性设计到有可见状态改变的副作用,也许我的记忆力正在消退,但我似乎无法回想起使用DateTime.Now的任何人的例子,并期望每次出现相同的值。至少在任何情况下,他们都不会像假设的DateTime.Now()一样严重搞砸它。

  • 导致可观察到的副作用 - 这当然正是属性首先被发明为语言特征的原因。微软自己的Property Design指南表明,制定者顺序并不重要,否则会暗示temporal coupling。当然,你不能单独实现与字段的时间耦合,但这仅仅是因为你不能单独使用字段导致任何有意义的行为发生,直到某些方法被执行。

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

属性可以执行哪些字段不能包含的其他内容:

  • Lazy loading,是防止初始化顺序错误的最有效方法之一。
  • Change Notifications,几乎是MVVM架构的完整基础。
  • Inheritance,例如定义一个抽象的TypeName这样派生的类可以提供有趣的但仍然是关于自己的恒定元数据。
  • Interception,感谢上述内容。
  • Indexers,每个曾经不得不与COM互操作的人以及Item(i)来电的不可避免的呕吐都会被认为是一件好事。
  • 使用PropertyDescriptor,这对于创建设计器和XAML框架至关重要。
Richter显然是一位多产作家,对CLR和C#了解很多,但我不得不说,看起来他最初写这篇建议时(我不确定他是否在他的建议中)最近的修订 - 我真心希望不是)他只是不想放弃旧习惯,而且很难接受C#的约定(比如C ++)。

我的意思是,他的"属性被认为是有害的"参数本质上归结为单个语句:属性看起来像字段,但它们可能不像字段一样。语句的问题是,它不是真的,或者至多它具有很强的误导性。属性不要看起来像字段 - 至少,它们 看起来像字段。

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

  1. 字段始终是私有的,从不公开。
  2. 字段应在camelCase中声明。属性是PascalCase。
  3. 因此,Foo.Bar = 42是属性访问者还是字段访问者没有歧义。它是一个属性访问器,应该被视为任何其他方法 - 它可能很慢,它可能会抛出异常,等等Abstraction的性质 - 它完全是上升的由宣告班斟酌决定如何反应。班级设计师应该应用最少惊喜的原则,但是呼叫者不应该对某个属性做任何假设,除非它按照它在锡上所说的那样做。 这是故意的。

    属性的替代方法是getter / setter方法。这是Java的方法,而且它是controversial since the beginning。如果这是你的包,那就好了,但这并不是我们在.NET阵营中的表现。我们至少在静态类型系统的范围内尝试避免福勒所谓的Syntactic Noise。我们不需要额外的括号,额外的get / set疣或额外的方法签名 - 如果我们能够在不失去清晰度的情况下避免它们的话,就不会这样做。

    说出你喜欢的任何内容,但foo.Bar.Baz = quux.Answers[42]总是比foo.getBar().setBaz(quux.getAnswers().getItem(42))更容易阅读。当你每天阅读数以千计的这一行时,它会产生影响。

    (如果您对上段的自然反应是,"确定它难以阅读,但如果您将其拆分为多行会更容易" ,我很遗憾地说你完全错过了这一点。)

答案 4 :(得分:11)

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

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

答案 5 :(得分:9)

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

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

至于有属性的警告,有一对。一个可能是滥用财产,另一个可能是微妙的。

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

E.g。

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; 
   } 
}

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

这是我唯一的警告。我认为物业的好处远大于问题。

答案 6 :(得分:6)

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

答案 7 :(得分:6)

我无法回答Jeffrey Richter的观点:

  

属性可以是只读的或只写的;字段访问始终是可读写的。

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

  

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

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

答案 8 :(得分:5)

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

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

  • 在getter / setter属性中没有耗时的操作
  • 属性getter没有副作用(多次调用,不会改变结果)

不幸的是,我看过那些不那么表现的属性。但问题不在于物业本身,而在于实施它们的人。所以它只需要一些教育。

答案 9 :(得分:4)

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

答案 10 :(得分:4)

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

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

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

答案 11 :(得分:3)

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

答案 12 :(得分:3)

The argument assumes that properties are bad because they look like fields, but can do surprising actions. This assumption is invalidated by .NET programmers' expectactions:

Properties don't look like fields. Fields look like properties.

• A property method may throw an exception; field access never throws an exception.

So, a field is like a property that is guaranteed to never throw an exception.

• A property cannot be passed as an out or ref parameter to a method; a field can.

So, a field is like a property, but it has additional capabilities: passing to a ref/out accepting methods.

• A property method can take a long time to execute; field access always completes immediately. [...]

So, a field is like a fast property.

• If called multiple times in a row, a property method may return a different value each time; a field returns the same value each time. The System.DateTime class has a readonly Now property that returns the current date and time.

So, a field is like a property that is guaranteed to return the same value unless the field was set to a different value.

• A property method may cause observable side effects; field access never does.

Again, a field is a property that is guaranteed to not do that.

• A property method may require additional memory or return a reference to something that is not actually part of the object's state, so modifying the returned object has no effect on the original object; querying a field always returns a reference to an object that is guaranteed to be part of the original object's state. Working with a property that returns a copy can be very confusing to developers, and this characteristic is frequently not documented.

This one may be surprising, but not because it's a property that does this. Rather, it's that barely anyone returns mutable copies in properties, so that 0.1% case is surprising.

答案 13 :(得分:0)

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

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