当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么我要避免在C#中使用Properties?》经验,为你挑选了9个好方法。

在他出色的着作"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),则调用property方法将非常慢,因此,某个方法比属性更受欢迎.在我看来,派生自MarshalByRefObject的类永远不应该使用属性.

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

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

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



1> Jon Skeet..:

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

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

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

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



2> Hejazzman..:

好吧,让我们逐一论证:

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

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

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

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

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

公平.

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

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

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

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

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

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

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

公平.

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

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

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


_"问题可以通过更好的语法突出显示来解决"_:你经常使用公共字段吗?私有字段通常具有不同的样式,例如`_field`.或者甚至是小写的`field`.

3> jalf..:

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

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

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

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

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

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

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

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

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

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

属性......不能.

太糟糕了.

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


我的论点是你不应该假设它是一个开头的字段 - 因为如果你从任何其他类调用它,你无论如何都不应该访问非常量字段,因为它们应该是私有的.(还有通知命名约定.)

4> Aaronaught..:

早在2009年,这个建议似乎只是对Who Moved My Cheese品种的追逐.今天,它几乎可笑地过时了.

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

是的,属性可以:

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

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

需要很长时间才能执行.这里的有效比较是使用方法,这些方法需要相同的时间 - 而不是字段.除了一位作者的个人偏好之外,没有给出"首选方法"的陈述.

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

如果我们谈论的是没有传入耦合的单线程访问的具体(和实际学术)情况,那么很明白,只有糟糕的属性设计才能产生可​​见状态变化的副作用,也许我的记忆是褪色,但我似乎无法想起任何人们使用DateTime.Now和期望每次出现相同价值的例子.至少在任何情况下,他们都不会把它搞砸了,就像假设一样糟糕DateTime.Now().

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

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

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

延迟加载,是防止初始化顺序错误的最有效方法之一.

更改通知,这几乎是MVVM架构的完整基础.

继承,例如定义抽象TypeName类似派生类可以提供有趣但但仍然是关于自身的恒定元数据.

拦截,感谢上述.

索引器,每个人都曾经不得不使用COM互操作和不可避免的Item(i)呼叫将被认为是一件美妙的事情.

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

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

我的意思是,他的"属性被视为有害"的论点基本上归结为一个单一的陈述:属性看起来像字段,但它们可能不像字段那样.声明的问题是,这不是真的,或者至多它具有很强的误导性.属性并不像场-至少,他们不应该看起来像场.

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

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

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

因此,对于Foo.Bar = 42属性访问器还是字段访问器没有任何歧义.它是一个属性访问器,应该被视为任何其他方法 - 它可能很慢,它可能会抛出异常,等等.这是抽象的本质- 它完全取决于声明类如何反应的自由裁量权.班级设计师应该应用最少惊喜的原则,但是呼叫者不应该对某个属性做任何假设,除非它按照它在锡上所说的那样做.这是故意的.

属性的替代方法是getter/setter方法.这是Java方法,从一开始就存在争议.如果那是你的包,那就好了,但这不是我们在.NET阵营中的表现.我们尝试,至少在静态类型系统的范围内,以避免福勒称之为句法噪声.我们不需要额外的括号,额外get/ set疣或额外的方法签名 - 如果我们可以避免它们而不会丢失任何清晰度.

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

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



5> Konstantin T..:

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

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



6> Mark Simpson..:

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

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

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

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

例如

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

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

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



7> Rashack..:

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



8> Cecil Has a ..:

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

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

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

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

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



9> M4N..:

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

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

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

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

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

推荐阅读
放ch养奶牛
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有