字段和属性之间有什么区别?

在 C#中,是什么使字段不同于属性,以及何时应使用字段代替属性?

答案

属性公开字段。字段(几乎总是)应该对类保持私有,并可以通过 get 和 set 属性对其进行访问。属性提供了一个抽象级别,允许您更改字段,而又不影响使用类的事物访问字段的外部方式。

public class MyClass
{
    // this is a field.  It is private to your class and stores the actual data.
    private string _myField;

    // this is a property. When accessed it uses the underlying field,
    // but only exposes the contract, which will not be affected by the underlying field
    public string MyProperty
    {
        get
        {
            return _myField;
        }
        set
        {
            _myField = value;
        }
    }

    // This is an AutoProperty (C# 3.0 and higher) - which is a shorthand syntax
    // used to generate a private field for you
    public int AnotherProperty{get;set;} 
}

@Kent 指出,不需要 Properties 来封装字段,它们可以对其他字段进行计算或用于其他目的。

@GSS 指出,当访问属性时,您还可以执行其他逻辑,例如验证,这是另一个有用的功能。

面向对象的编程原理说,一个类的内部工作应该对外界隐藏。如果公开一个字段,则本质上就是公开该类的内部实现。因此,我们使用属性(或 Java 中的方法)包装字段,以使我们能够更改实现而无需依赖我们的代码。看起来我们可以将逻辑放入属性中,这也使我们能够在需要时执行验证逻辑等。 C#3 具有自动属性的可能令人困惑的概念。这使我们可以简单地定义属性,而 C#3 编译器将为我们生成私有字段。

public class Person
{
   private string _name;

   public string Name
   {
      get
      {
         return _name;
      }
      set
      {
         _name = value;
      }
   }
   public int Age{get;set;} //AutoProperty generates private field for us
}

一个重要的区别是接口可以具有属性,但不能具有字段。对我而言,这强调了应使用属性来定义类的公共接口,而字段则应在类的私有内部工作中使用。通常,我很少创建公共字段,并且类似地,我很少创建非公共属性。

我将为您提供一些使用属性的示例,这些属性可能会导致齿轮旋转:

  • 延迟初始化如果您拥有一个对象的属性,该属性的加载成本很高,但是在正常的代码运行中却没有得到太多访问,则可以通过该属性延迟其加载。这样,它就坐在那里,但是当另一个模块第一次尝试调用该属性时,它会检查基础字段是否为空 - 如果是,它将继续进行加载并将其加载,而调用模块则未知。这样可以大大加快对象的初始化速度。
  • 脏跟踪:我实际上是从我在 StackOverflow 上自己的问题中学到的。当我有很多对象的值在运行中可能已更改时,我可以使用该属性来跟踪是否需要将它们保存回数据库。如果对象的单个属性没有更改,则 IsDirty 标志不会被触发,因此,在确定需要返回数据库的内容时,保存功能将跳过该标志。

使用属性,可以在更改属性值(也称为 PropertyChangedEvent)时或在更改值以支持取消之前引发事件。

使用(直接访问)字段是不可能的。

public class Person {
 private string _name;

 public event EventHandler NameChanging;     
 public event EventHandler NameChanged;

 public string Name{
  get
  {
     return _name;
  }
  set
  {
     OnNameChanging();
     _name = value;
     OnNameChanged();
  }
 }

 private void OnNameChanging(){
   EventHandler localEvent = NameChanging;
   if (localEvent != null) {
     localEvent(this,EventArgs.Empty);
   }
 }

 private void OnNameChanged(){
   EventHandler localEvent = NameChanged;
   if (localEvent != null) {
     localEvent(this,EventArgs.Empty);
   }
 }
}

由于其中许多人已经解释了PropertiesField技术利弊,因此现在该开始研究实时示例了。

1. 属性允许您设置只读访问级别

考虑dataTable.Rows.CountdataTable.Columns[i].Caption 。它们来自DataTable类,并且都对我们公开。它们的访问级别的区别在于,我们无法将值设置为dataTable.Rows.Count但可以读写dataTable.Columns[i].Caption 。通过Field可以做到吗?没有!!!这只能通过Properties来完成。

public class DataTable
{
    public class Rows
    {       
       private string _count;        

       // This Count will be accessable to us but have used only "get" ie, readonly
       public int Count
       {
           get
           {
              return _count;
           }       
       }
    } 

    public class Columns
    {
        private string _caption;        

        // Used both "get" and "set" ie, readable and writable
        public string Caption
        {
           get
           {
              return _caption;
           }
           set
           {
              _caption = value;
           }
       }       
    } 
}

2. PropertyGrid 中的属性

您可能已经在 Visual Studio 中使用Button 。它的属性显示在PropertyGrid例如TextName等。当我们拖放按钮时,当我们单击属性时,它将自动找到Button类并过滤Properties并在PropertyGrid显示该PropertyGrid (其中不会显示PropertyGrid Field ,即使它们是公共的)。

public class Button
{
    private string _text;        
    private string _name;
    private string _someProperty;

    public string Text
    {
        get
        {
           return _text;
        }
        set
        {
           _text = value;
        }
   } 

   public string Name
   {
        get
        {
           return _name;
        }
        set
        {
           _name = value;
        }
   } 

   [Browsable(false)]
   public string SomeProperty
   {
        get
        {
           return _someProperty;
        }
        set
        {
           _someProperty= value;
        }
   }

PropertyGrid ,将显示属性NameText ,但不显示SomeProperty 。为什么???因为 Properties 可以接受Attributes 。如果[Browsable(false)]为 false,则不会显示。

3. 可以在属性内执行语句

public class Rows
{       
    private string _count;        


    public int Count
    {
        get
        {
           return CalculateNoOfRows();
        }  
    } 

    public int CalculateNoOfRows()
    {
         // Calculation here and finally set the value to _count
         return _count;
    }
}

4. 在绑定源中只能使用属性

绑定源可以帮助我们减少代码行数。 BindingSource不接受Fields 。我们应该为此使用Properties

5. 调试模式

考虑我们正在使用Field来保存值。在某些时候,我们需要调试并检查该字段的值在哪里变为空。如果代码行数超过 1000,将很难做到。在这种情况下,我们可以使用Property并可以在Property内设置调试模式。

public string Name
   {
        // Can set debug mode inside get or set
        get
        {
           return _name;
        }
        set
        {
           _name = value;
        }
   }

差异 - 用途(何时以及为什么)

字段是直接在类或结构中声明的变量。一个类或结构可以具有实例字段或静态字段,或两者都有。通常,应将字段用于具有私有或受保护的可访问性的变量 。您的类公开给客户端代码的数据应通过方法,属性和索引器提供。通过使用这些结构间接访问内部字段,可以防止输入值无效。

属性是提供灵活机制以读取,写入或计算私有字段的值的成员。可以将属性当作公共数据成员使用,但实际上它们是称为accessors 的特殊方法。这使数据易于访问,并且仍然有助于提高方法的安全性和灵活性 。属性使类可以公开获取和设置值的公共方式,同时隐藏实现或验证代码。获取属性访问器用于返回属性值,而设置访问器用于分配新值。

属性的主要优点是允许您更改访问对象上数据的方式而不会破坏其公共接口。例如,如果您需要添加额外的验证,或者将存储的字段更改为计算字段,那么如果您最初将该字段公开为属性,则可以轻松地做到这一点。如果您只是直接公开一个字段,则必须更改类的公共接口以添加新功能。这种更改将破坏现有的客户端,要求他们重新编译后才能使用新版本的代码。

如果编写用于广泛使用的类库(例如数百万人使用的. NET Framework),则可能会出现问题。但是,如果您要在一个小的代码库(例如 <= 5 万行)内编写一个内部使用的类,那实际上就没什么大不了的,因为没有人会受到您的更改的不利影响。在那种情况下,这实际上取决于个人喜好。

在后台将属性编译为方法。因此,将Name属性编译为get_Name()set_Name(string value) 。如果您研究编译的代码,则可以看到此信息。因此,使用它们时(非常)很小的性能开销。通常,如果将字段暴露在外部,则将始终使用属性;如果需要验证值,则经常在内部使用它。

属性支持非对称访问,即,您可以具有 getter 和 setter 或仅是两者之一。类似地,属性支持对 getter / setter 的个别可访问性。字段始终是对称的,即,您始终可以同时获取和设置值。唯一的例外是只读字段,在初始化后显然无法设置该字段。

属性可能会运行很长时间,有副作用,甚至可能引发异常。字段速度快,没有副作用,并且永远不会抛出异常。由于副作用,属性可能会为每个调用返回不同的值(如 DateTime.Now 可能就是这样,即 DateTime.Now 并不总是等于 DateTime.Now)。字段始终返回相同的值。

字段可以用于 out / ref 参数,而属性可以不使用。属性支持其他逻辑–可以用于实现延迟加载等。

属性通过封装获取 / 设置值的含义来支持抽象级别。

在大多数情况下都使用属性,但要避免产生副作用。