C# 3.0+中属性和字段的区别

143

我意识到这似乎是C#中字段和属性的区别是什么?的重复,但我的问题有一点不同(从我的角度来看):

一旦我知道

  • 我不会使用 "仅适用于属性的技术" 来使用我的类,并且
  • 我不会在 getter/setter 中使用验证代码。

除了样式/未来开发等方面的差异外,是否还有其他差异,比如在设置属性时某种类型的控制?

还有其他区别吗?

public string MyString { get; set; }
并且
public string myString;

我知道第一个版本需要C# 3.0或以上版本,编译器会创建私有字段。


可能是重复的问题:自动实现的getter和setter与公共字段相比 - nawfal
10个回答

169

字段和属性看起来很相似,但它们并不相同。属性是方法,因此有些事情对于属性来说不支持,而且有一些事情可能会发生在属性中,但在字段的情况下从未发生。

以下是它们之间的一些区别:

  • 字段可用作out/ref参数的输入。属性则不能。
  • 当多次调用时,字段将始终产生相同的结果(如果我们排除多线程问题)。而诸如DateTime.Now之类的属性并非始终等于自身。
  • 属性可能会引发异常,而字段永远不会这样做。
  • 属性可能具有副作用或需要执行很长时间。字段没有副作用,并且始终可以以给定类型所期望的速度运行。
  • 属性支持不同的getter/setter可访问性,而字段则不支持(但字段可以设为readonly)。
  • 在使用反射时,属性和字段被视为不同的MemberTypes,因此它们位于不同的位置(例如GetFieldsGetProperties)。
  • JIT编译器可能会将属性访问与字段访问处理得非常不同。但是,它可能会编译成相同的本机代码,但存在差异的余地。

13
请注意,如果采用良好的实践方法,其中一些要点应该不会存在差异。也就是说,属性确实不应该具有副作用,也不应该执行时间过长。 - Noldorin
17
我同意,但不幸的是,“should”在这里是关键词。使用字段可以保证行为。我并不是说你应该使用字段,但了解语义上的区别是很重要的。 - Brian Rasmussen
4
好的,说得对。很多初学者程序员对这些东西一窍不通,不幸的是...... - Noldorin
2
此外,字段可以有字段初始化器,而属性必须在构造函数中初始化。 - Dio F
3
我认为这个答案比被采纳的答案好了好几个数量级。我开始认为总是优先选择属性而不是字段的“可接受”方式是错误的思维方式。如果你只需要处理数据,使用字段。如果你需要对数据应用功能,使用方法。由于属性可能具有您没有意识到的副作用(特别是如果您没有设计库并且文档很少),它们在大多数情况下似乎与直觉相反。 - David Peterson
显示剩余3条评论

118

封装。

在第二个例子中,你仅仅定义了一个变量,在第一个例子中,对变量进行了getter/setter设置。所以如果你决定稍后验证该变量,这将会更加容易。

而且它们在Intellisense中显示的方式也不同 :)

编辑: 为OP更新的问题更新 - 如果你想忽略其他建议,另一个原因是它并不是良好的OO设计。如果你没有非常好的理由,始终选择属性而非公共变量/字段。


12
为什么会更容易?我把一个字段变成属性并添加一个私有备份字段有什么阻碍吗?这对调用代码有何影响? - Serge Wautier
33
它会影响已经编译好的代码。例如,如果您正在开发一个被多个应用程序使用的库,在该库中将一个字段更改为属性将需要重新编译每个应用程序。如果它是一个属性,您可以更新属性而无需担心。 - Dustin Campbell
26
如果被调用的代码总是与受影响的类同时重新编译(因此任何私有或内部的内容没有显示给外部的部分都是100%安全的),那么将其作为字段是完全可以的。 - ShuggyCoUk
1
哇,Shuggy,你的评论正是我在寻找的答案! - p4bl0
@ShuggyCoUk 这就是我在寻找的答案。 - styfle
显示剩余4条评论

43

有一些明显的区别:

  1. 属性可以有访问器关键字。

    public string MyString { get; private set; }
    
  2. 属性可以在子元素中被覆盖。

  3. public virtual string MyString { get; protected set; }
    

1
嗯,第二个很有趣...我没有想到过。 - p4bl0

14

基本的差异在于,字段是存储指定类型数据的内存位置。属性代表一到两个代码单元,用于检索或设置指定类型的值。通过使用行为类似于字段的成员(可以出现在赋值运算符的任一侧),访问器方法的使用在语法上是隐藏的。


11

访问器不仅仅是字段。其他人已经指出了一些重要的差异,我将再添加一个。

属性参与接口类。例如:

interface IPerson
{
    string FirstName { get; set; }
    string LastName { get; set; }
}

这个接口可以通过多种方式满足。例如:

class Person: IPerson
{
    private string _name;
    public string FirstName
    {
        get
        {
            return _name ?? string.Empty;
        }
        set
        {
            if (value == null)
                throw new System.ArgumentNullException("value");
            _name = value;
        }
    }
    ...
}
在此实现中,我们既保护了Person类不会进入无效状态,也保护了调用者不会从未被赋值的属性中获得null。但是我们可以将设计推得更远。例如,接口可能不需要处理setter方法。可以合法地说,IPerson接口的使用者只对获取属性感兴趣,而不关心设置它:
interface IPerson
{
    string FirstName { get; }
    string LastName { get; }
}

之前的Person类实现满足了这个接口。从消费者(消费IPerson的人)的角度来看,调用方设置属性的事实是无意义的。具体实现的其他功能由建造者等考虑。

class PersonBuilder: IPersonBuilder
{
    IPerson BuildPerson(IContext context)
    {

        Person person = new Person();

        person.FirstName = context.GetFirstName();
        person.LastName = context.GetLastName();

        return person;

    }
}

...

void Consumer(IPersonBuilder builder, IContext context)
{
    IPerson person = builder.BuildPerson(context);
    Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}

在这段代码中,消费者不需要知道属性设置器的存在——他不需要知道这些。消费者只需要获取器,并从接口(即合同)中获取获取器。

IPerson 的另一个完全有效的实现是一个不可变的人类和相应的人类工厂:

class Person: IPerson
{
    public Person(string firstName, string lastName)
    {

        if (string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName))
            throw new System.ArgumentException();

        this.FirstName = firstName;
        this.LastName = lastName;

    }

    public string FirstName { get; private set; }

    public string LastName { get; private set; }

}

...

class PersonFactory: IPersonFactory
{
    public IPerson CreatePerson(string firstName, string lastName)
    {
        return new Person(firstName, lastName);
    }
}
...
void Consumer(IPersonFactory factory)
{
    IPerson person = factory.CreatePerson("John", "Doe");
    Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}
在这个代码示例中,消费者再次不知道如何填写属性。消费者只处理获取器和具体实现(以及背后的业务逻辑,例如测试名称是否为空),而专门的类 - 构建器和工厂则完成了其余操作。所有这些操作在字段中是完全不可能的。

8

第一个:

public string MyString {get; set; }

MyString是一个属性;第二个(public string MyString)是一个字段。

区别在于,某些技术(例如ASP.NET数据绑定)仅适用于属性,而不适用于字段。同样适用于XML序列化:只有属性被序列化,字段不被序列化。


8
错误。XML序列化确实会序列化公共字段。 - Serge Wautier
2
也许吧。但是当你从一个类创建一个对象数据源时,你只能使用属性,而不能使用字段。(除非我做错了什么 :P) - Svish
好的编程习惯是DRY(Don't Repeat Yourself);但我还是会再写一遍。我喜欢C#语言中属性的强大作用,它的实现比Java要好得多(因此从一开始就如此)。许多,甚至所有的.NET解决方案都只使用属性。WPF、ASPX等等。 - Jacek Cz

2

属性和字段在许多情况下可能看起来相似,但实际上并不相同。属性存在着对字段不存在的限制,反之亦然。

正如其他人所提到的,通过将其访问器设置为private,您可以使属性变为只读或只写。但是对于字段来说,您无法这样做。属性还可以是虚拟的,而字段则不能。

将属性视为getXXX()/setXXX()函数的语法糖即可。这就是它们在幕后实现的方式。


1
在其他答案和例子中,我认为这个例子在某些情况下是有用的。
例如,假设您有一个像以下一样的OnChange属性:OnChange property
public Action OnChange { get; set; }

如果您想使用委托,那么您需要将OnChange更改为field,如下所示:
public event Action OnChange = delegate {};

在这种情况下,我们会保护我们的领域免受未经授权的访问或修改。

1

字段和属性之间还有一个重要的区别。

在使用WPF时,只能绑定公共属性。绑定公共字段将不会起作用。即使没有实现INotifyPropertyChanged(虽然您总是应该这样做),这也是正确的。


好的编程习惯是DRY(Don't Repeat Yourself);但我还是会再写一遍。我喜欢C#语言中属性的强大作用,它的实现比Java要好得多(因此从一开始就如此)。许多,甚至所有的.NET解决方案都只使用属性。WPF、ASPX等等。 - Jacek Cz

0
您应该始终使用属性而不是公共字段。这样可以确保您的库在未来需要实现封装任何字段时都具有此功能,而不会破坏现有代码。如果您在现有库中用属性替换字段,则使用您的库的所有依赖模块也需要重新构建。

“always” 是一个很难的词。在 C#中(比 Java 好),属性具有强大的地位,是 ASP、WPF 等程序中“绑定”的主要方法(可能没有例外)。但尽管如此,我可以想象出一种设计,即字段不是属性,这也有一定的意义(有时候)。 - Jacek Cz

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