成员变量和成员属性有什么区别?

17

有时候我会在类的顶部声明成员变量,然后再声明一个属性来访问或设置该成员变量,但是如果变量只会从类内部访问和设置,那么我会想这个属性是否必要。那么,使用属性来访问和设置成员变量与直接对成员变量进行操作相比,有什么优势呢?下面是一个例子:

public class Car
{

    int speed; //Is this sufficient enough if Car will only set and get it.

    public Car(int initialSpeed)
    {
        speed = initialSpeed;
    }

    //Is this actually necessary, is it only for setting and getting the member
        //variable or does it add some benefit to it, such as caching and if so,
        //how does caching work with properties.
    public int Speed 
    {
        get{return speed;}
        set{speed = value;}
    }

        //Which is better?
        public void MultiplySpeed(int multiply)
        {
            speed = speed * multiply; //Line 1
            this.Speed = this.Speed * multiply; //Line 2

            //Change speed value many times
            speed = speed + speed + speed;
            speed = speed * speed;
            speed = speed / 3;
            speed = speed - 4;

        }
}
在上面的代码中,如果我没有Speed属性来设置和获取变量speed,并且我决定将int speed更改为int spd,那么我必须在使用到speed的所有地方都改为spd。但是,如果我使用类似Speed这样的属性来设置和获取speed,那么我只需要在属性的get和set中将speed改为spd,这样在我的MutilplySpeed方法中,像上面的this.Speed = this.Speed + this.Speed + this.Speed这样的代码就不会出问题。

可能是在C#中字段和属性的区别是什么?的重复问题。 - nawfal
8个回答

25
如果变量是 private,我通常不会为它创建属性。如果以任何方式暴露在类型之外,我总是通过属性进行暴露,出于以下不同的原因:
  • 可能今天不必要,但如果以后需要就会造成破坏性的更改。
  • 数据绑定仅适用于属性,而不适用于字段(我认为,不是一个大的数据绑定用户)。
  • 它允许在访问值时插入验证、日志和断点。
此外,如果该字段通过属性进行了暴露,我总是通过属性访问它,即使在类内部也是如此。 更新
针对您更新的代码示例,有一些需要考虑的代码设计问题。
  • 可读性与速度
  • "原子性"
  • 其他副作用
一个典型的建议(我觉得非常好的)是 "写清晰的代码,测试性能"。这意味着当你编写代码时,你首要关注的应该是,在查看代码时是否清晰明了地表达了它的功能。这通常(但并不总是)比代码的原始速度更重要。只有在确定可以获得优化后才编写速度优化的代码。访问属性将比直接读取字段稍微慢一些,但在大多数情况下,差异将是可以忽略的(如果有可测量的差异)。 原子性可能会成为一个问题。给定您的代码示例,我们有字段 speed,通过属性 Speed 进行公开。如果方法 MultiplySpeed 需要对值执行多个更新,在计算正在进行时,这些中间值将在不同的时间通过 Speed 属性可用。无论是直接更新字段还是通过属性进行更新,都是如此。在这种情况下,最好先将值放入本地变量中,在计算中使用该变量,完成后将该变量的值赋回到属性。

最后,其他的副作用。改变 Speed 的值可能应该触发一个事件(例如 SpeedChanged)。在这种情况下,直到计算完成之前最好不要进行更新。

我喜欢将属性看作是一个 契约,字段则是实现。除了我的类型核心部分外,任何人需要值都应该使用此契约。只有在有充分理由绕过契约时才可以依赖于实现。

如果您封装了对字段的访问,则自然而然地更改字段名称将需要较少的更新(也许字段名称变得不那么重要)。

希望这样说得有道理,并且不太偏离主题;)


1
@Rob:通过反射也可以访问/设置字段(Type.GetFieldsFieldInfo.GetValueFieldInfo.SetValue)。 - Fredrik Mörk
1
谢谢提供信息。我有一个问题。关于你的第一点:你是说如果我将变量speed更改为spd,我将不得不浏览所有代码并更改我使用变量speed的每个地方并将其更改为spd,但如果我使用Speed这样的属性,我只需要在get和set中将speed更改为spd?我理解得对吗? - Xaisoft
@Xaisoft:我认为你说得对(如果我理解你的文本正确的话)。 - Fredrik Mörk
我会更新我的代码,这样你就可以看到我的意思了。然后你可以告诉我我是否正确。 - Xaisoft
@Fredrik Mörk,你是指原子性吧...我想。 - Agnel Kurian
显示剩余5条评论

8
我同意Frederik的回答。遵循他的建议的一件事是使用自动属性,这样可以稍微减少一些工作量。自动属性只是自动生成标准getter/setter逻辑的属性。您不会得到任何验证,但是您始终可以稍后用标准属性替换自动属性。此替换不会造成破坏性变化。
在此示例中,我将Speed属性替换为自动属性。请注意,成员变量消失了,您的类必须通过属性访问它。
public class Car
{
    public Car(int initialSpeed)
    {
        Speed = initialSpeed;
    }

    public int Speed { get; set; }

    public void MultiplySpeed(int multiply)
    {
        Speed *= multiply;
    }
}

你还可以使用另一种称为“具有私有设置”的变体。这意味着getter是公共的,但setter是私有的。你可以像这样定义它:

    public int Speed { get; private set; }

关于你提到的 this. 前缀的问题,通常它是无关紧要的。唯一需要注意的时候是当你定义了一个方法参数或者局部变量和成员变量同名时,你可以使用 this 来访问成员变量。

1

像验证这样的事情可以在一个地方处理。成员被封装起来,你不必担心来自类的其余部分的验证和其他问题。

在您目前的场景中,这并没有真正区别,但当您需要更改变量或需要添加行为时,使用属性会更容易,因为您只需要更改它的一个地方。


1

它不会添加缓存,但它确实允许一致的接口。

想象一下,将来必须通过添加常量来修改速度。使用成员变量将很困难,而属性允许进行此操作。

此外,在类内部,为了保持一致性,应再次访问该属性(想象一下上述情况,其中一个类直接访问了成员变量)。


1

我所知道的唯一真正的原因是,如果从程序集外部访问该字段。这种情况下,如果您想向该字段添加轻量级功能(例如设置脏标记或验证更改),则必须将其更改为属性,这会改变调用程序集查看它的方式,还需要重新编译。在非常罕见的情况下,您可能发现无法控制该程序集,那么您就有了问题。

不要让OO狂热者告诉您使用公共字段在哲学上是错误的,尽管我可能同意自动属性使争论有点无意义。


2
不要让面向对象的狂热者告诉你使用公共字段在哲学上是错误的,尽管我可能同意自动属性使这个论点有些无关紧要。我不是面向对象的狂热者,但那是一种不好的做法。所有成员变量都应该是私有的。 - JonH
为什么,JonH?别误会,我曾经也是这么想的;说实话,如果出于习惯,我现在还是这么做。但是如果要解释为什么,我没有比刚才给出的更好的答案。 - pdr
@JonH - 发表“所有成员变量都应该是私有的”这样的笼统之论,对于在不同条件下、追求不同目标的人来说并不公平。我发现性能降低是无法容忍的 - 但也许我应该使用 C 和结构体?从你的角度来看,那应该更糟糕吧。 - phkahler
@pdr,你用“狂热者”这样的标签来描述像JonH这样的人,不太可能帮助任何人,我也可以使用几个词语(不公正)负面暗示地说:“他的背景与我的不同”。 - phkahler
我只有在想要公开一个(通常是静态的)不会改变且经常使用的值时才使用公共字段。例如,考虑 EventArgs.Empty 和 String.Empty。值得记住的是,在 .Net 的“对象派生”世界中,除非将克隆逻辑放在 getter 中,否则属性与封装不可比较。它们的使用受到与 c++ 中 setter 和 getter 不同的原则的约束。从根本上讲,它们在反射时提供了一个单独的作用域,使得绑定额外逻辑(如 INotifyPropertyChanged)成为可能,并让您控制访问方向。 - Gusdor
看起来JonH没有回答提出的问题,所以我会简单地补充一下通常最好将成员变量设置为私有,这样它们就不会与其他类耦合。如果你需要更改成员名称或具体表示,你会导致在其他任何使用它的地方都会产生破坏性的变化。然而,如果你将其隐藏在属性后面,你可以自由地更改其特征,而不会破坏其他地方的代码(假设你可以保持属性的标识不变)。 - Derek

1
事实上,如果没有额外的逻辑,公开声明的字段和具有私有后备存储的公共属性之间并没有太大的区别。尽管如此,使用属性仍被认为是最佳实践。
在所有人都关注可扩展性之前,请记住,如果您以后需要添加功能,可以保留属性名称并为后备存储引入新名称,以避免破坏性更改。

0

你忘了提到一件事,属性将在你扩展类时帮助你。

如果你的类被正确设计,基类内部的变量应该是private的。但是没有实际的public属性,你将无法从扩展的类中访问这些私有变量。我们正在谈论公共与私有,我不包括受保护的原因 :)

这只是值得一提的一些注意事项:

  • 当一个类被扩展时,属性会提供帮助。
  • 对我来说,属性使代码更易读(此外,这个私有变量版本的公共属性变量名)。
  • 属性可以确保只读、私有设置、公共获取等(对其他程序员更易读)。考虑一个标识符需要公共获取但私有设置的情况。
  • 就我个人而言,过多的获取/设置似乎会使代码复杂化,使代码不易读,增加了太多不必要的语法。
  • 继承/扩展到扩展类不允许您继承私有变量,属性是答案。(同样没有提到受保护的内容,那是另一回事)。
  • 对我来说,即使类有一个私有变量,我的类方法仍然使用属性来访问或使用该私有变量。
  • 不要忘记验证,它使验证特别易于进行,尤其是从可读性的角度来看。

这些只是一些常见的事情(我的个人意见)。


那么根据你的回答,我理解通过将变量设为私有并使用属性来设置和获取它们,我正在利用封装的优势? - Xaisoft
您也可以使用受保护的变量来实现这一点。 - cyberconte

0
在我看来,这种语言设计是有缺陷的。不应该有两种做事方式,它们之间有如此多的语义重叠。属性/字段应该根据使用方式无缝地提供任一方法的优点。如果程序最少使用属性特性,则它们应该像字段一样运作。此时,也不需要声明空的get;和set;方法。这些差异让我感到人为。
这是一种很棒的语言;而且大部分都很干净。但这并不意味着下一次就不应该对其进行改进。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接