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


140

我意识到这似乎是C#中的字段和属性之间的区别什么的副本但是我的问题(从我的角度来看)略有不同:

一旦我知道

  • 我不会将我的课程与“仅适用于房地产的技术”结合使用,并且
  • 我不会在getter / setter中使用验证代码。

有什么区别(样式/未来开发除外),例如设置属性时的某种控件?

之间是否还有其他区别:

public string MyString { get; set; }

public string myString;

(我知道,第一个版本需要C#3.0或更高版本,并且编译器确实会创建专用字段。)


Answers:


117

封装。

在第二个实例中,您刚刚定义了一个变量,在第一个实例中,该变量周围有一个getter / setter方法。因此,如果您决定以后再验证变量-会容易得多。

另外,它们在Intellisense中的显示方式也不同:)

编辑:更新OPs更新的问题-如果您想在这里忽略其他建议,另一个原因是它根本不是一个好的OO设计。而且,如果您没有很好的理由这样做,请始终在公共变量/字段上选择一个属性。


9
为什么会更容易?是什么使我无法将字段转换为属性并添加私有支持字段?这如何影响调用代码?
Serge Wautier 09年

30
@Serge-它会影响已经编译的代码。例如,如果要开发供多个应用程序使用的库,则将字段更改为该库中的属性将需要重新编译每个应用程序。如果它是一个属性,则可以不用担心而更新该属性。
达斯汀·坎贝尔

我完全同意您的观点,我总是使用属性。我只是对可能的差异感到好奇
p4bl0

24
如果使用方始终在受影响的类的同时重新编译使用方的代码(因此对任何私有或内部无内部可见的内容都是100%安全的),那么将其设置为一个字段就可以了
ShuggyCoUk 2009年

1
哇你的评论正好是我在寻找的答案!
p4bl0

160

字段和属性看起来相同,但事实并非如此。属性是方法,因此某些属性不受支持,某些属性可能会发生,但对于字段则永远不会发生。

以下是差异列表:

  • 字段可用作out/ref参数的输入。属性不能。
  • 多次调用时,一个字段总是会产生相同的结果(如果我们忽略了多个线程的问题)。诸如之类的属性DateTime.Now并不总是等于其自身。
  • 属性可能会引发异常-字段永远不会那样做。
  • 属性可能会有副作用,或者执行时间会非常长。字段没有副作用,并且始终会像给定类型一样快。
  • 属性支持getter / setter的不同可访问性-字段不支持(但可以创建字段readonly
  • 使用反射时,属性和字段被视为不同,MemberTypes因此它们的位置有所不同(例如,GetFieldsvs GetProperties
  • 与现场访问相比,JIT编译器对待属性访问的处理方式可能非常不同。但是,它可以编译为相同的本机代码,但存在差异的范围。

11
但是请注意,几个这样的点应该不会,如果良好做法采用有差异。也就是说,属性实际上永远不会有副作用,也不需要花费很长时间来执行。
Noldorin 2012年

15
@Noldorin:我同意,但不幸的是应该是关键字在这里。使用字段可以保证行为。我并不是说您应该使用字段,但重要的是要注意语义上的差异。
Brian Rasmussen 2012年

4
是的,足够公平。不幸的是,初学者程序员对这些事情一无所知……
Noldorin 2012年

2
同样,字段可能具有字段初始化程序,而属性必须在构造函数中初始化。
Dio F

3
我觉得这个答案比公认的答案更好。我开始认为,总是优先选择属性而不是字段的“可接受”方法是一种不好的想法。如果只需要处理数据,请使用一个字段。如果需要将功能应用于数据,请使用一种方法。由于属性可能会产生您不知道的副作用(尤其是如果您没有设计库并且几乎没有文档),因此在大多数情况下,它们对我来说似乎很直观。
David Peterson 2014年

41

几个快速的明显差异

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

    public string MyString { get; private set; }
  2. 可以在后代中覆盖属性。

    public virtual string MyString { get; protected set; }

1
mmh..nr 2很有趣..我没想到
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类避免进入无效状态,也防止调用者从unassigned属性中获取空值。

但是我们可以进一步推动设计。例如,接口可能不处理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);
}

在此代码示例中,消费者再次不了解填充属性的知识。消费者只处理吸气剂和具体的实现(以及背后的业务逻辑,例如测试名称是否为空)留给专门的类-建筑商和工厂。使用字段完全不可能进行所有这些操作。


7

第一个:

public string MyString {get; set; }

是财产;第二个(public string MyString)表示一个字段。

不同之处在于某些技术(实例的ASP.NET数据绑定)仅适用于属性,不适用于字段。XML序列化也是如此:仅属性被序列化,字段未被序列化。


8
错误。XML序列化不会序列化公共字段。
Serge Wautier 09年

2
也许。但是,当您从类创建对象数据源时,只能使用属性,而不能使用字段。(除非我做错了:P)
Svish

对DRY来说不错;)但是我再次写,我喜欢C#语言中property的强大作用。比Java更好地实现(因此,从一开始)许多也许所有的.net解决方案仅在属性上运行。WPF,ASPX等。
Jacek Cz 2015年

3

在许多情况下,属性和字段可能看起来很相似,但事实却并非如此。字段不存在属性的限制,反之亦然。

正如其他人所提到的。您可以通过将属性的访问者设为私有来将其设置为只读或只写。您不能使用字段来执行此操作。属性也可以是虚拟的,而字段则不能。

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


1

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

使用WPF时,只能绑定到公共属性。绑定到公共领域将不会工作。即使不执行INotifyPropertyChanged(即使您总是应该这样做),也是如此。


对DRY来说不错;)但是我再次写,我喜欢C#语言中property的强大作用。比Java更好地实现(因此,从一开始)许多也许所有的.net解决方案仅在属性上起作用。WPF,ASPX等。
Jacek Cz 2015年

1

在其他答案和示例中,我认为该示例在某些情况下很有用。

例如,假设您有一个类似以下内容的人:OnChange property

public Action OnChange { get; set; }

如果要使用委托,则需要将其更改OnChangefield以下形式:

public event Action OnChange = delegate {};

在这种情况下,我们保护我们的领域免受不必要的访问或修改。


0

您应该始终对所有公共字段使用属性而不是字段,这可以确保您的库能够在将来需要时对任何字段实现封装而不会破坏现有代码。使用您的库的依赖模块也需要重建。


“总是”是个硬词。在C#(比Java中更好)中,属性具有很强的地位,(可能毫无例外)是ASP,WPF等中“绑定”的主要/方法。但是,尽管如此,我仍然可以想象没有属性的领域的设计是有意义的(有时)
Jacek Cz 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.