关于C:字段和属性之间的区别是什么?

What is the difference between a field and a property?

在C中,是什么使字段与属性不同?什么时候应该使用字段而不是属性?


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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指出,封装字段不需要属性,它们可以对其他字段进行计算,也可以用于其他目的。

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


面向对象的编程原则认为,类的内部工作应该隐藏在外部世界之外。如果公开一个字段,那么本质上就是公开类的内部实现。因此,我们用属性(或Java的方法中的方法)包装字段,使我们有能力在不依赖于我们的代码的情况下更改实现。我们可以在属性中放入逻辑,也可以在需要时执行验证逻辑等。C 3有可能混淆的自动属性概念。这允许我们简单地定义属性,C 3编译器将为我们生成私有字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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标志将不会被触发,因此在决定需要返回数据库时,保存功能将跳过它。


使用属性,您可以在属性值更改时引发事件(aka)。或者在将值更改为支持取消之前。

这在(直接访问)字段中是不可能的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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。属性网格中的属性

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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。为什么????因为属性可以接受属性。在[Browsable(false)]为假的情况下不显示。

三。无法在属性内执行语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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。绑定源中只能使用属性

绑定源帮助我们减少代码行数。Fields不被BindingSource接受。我们应该用Properties来解决这个问题。

5。调试模式

假设我们使用Field来保存一个值。在某个时刻,我们需要调试并检查该字段的值在哪里为空。如果代码行数超过1000行,就很难做到这一点。在这种情况下,我们可以使用Property,并且可以在Property内设置调试模式。

1
2
3
4
5
6
7
8
9
10
11
12
   public string Name
   {
        // Can set debug mode inside get or set
        get
        {
           return _name;
        }
        set
        {
           _name = value;
        }
   }


差异-用途(何时何地)

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

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


属性的主要优点是允许您在不破坏对象公共接口的情况下更改访问对象上数据的方式。例如,如果您需要添加额外的验证,或者将存储字段更改为计算字段,那么如果您最初将该字段作为属性公开,则可以很容易地执行此操作。如果您刚刚直接公开了一个字段,那么您必须更改类的公共接口以添加新功能。这种更改会破坏现有的客户机,要求在使用新版本的代码之前重新编译它们。

如果您编写一个为广泛使用而设计的类库(如.NET框架,它被数百万人使用),这可能是一个问题。但是,如果您正在编写一个在小代码库内部使用的类(例如<=50k行),这实际上不是什么大问题,因为没有人会受到您的更改的不利影响。在这种情况下,这实际上取决于个人喜好。


在后台,属性被编译为方法。因此,Name属性被编译为get_Name()set_Name(string value)属性。如果研究编译后的代码,就可以看到这一点。所以在使用它们时,性能开销很小。通常,如果向外部公开字段,则始终使用属性;如果需要验证值,则通常在内部使用属性。


属性支持非对称访问,即您可以拥有getter和setter,也可以只拥有其中一个。同样,属性支持getter/setter的单独可访问性。字段总是对称的,也就是说,您总是可以同时获取和设置值。例外情况是只读字段,初始化后显然无法设置。

属性可能运行很长时间,有副作用,甚至可能引发异常。字段很快,没有副作用,不会抛出异常。由于副作用,属性可能会为每个调用返回不同的值(对于datetime.now,即datetime.now,可能是datetime.now)。字段总是返回相同的值。

字段可以用于out/ref参数,属性不能。属性支持额外的逻辑——这可以用来实现延迟加载等。

属性通过封装获取/设置值的任何方法来支持抽象级别。

在大多数/所有情况下使用属性,但尽量避免副作用。


当您希望您的私有变量(字段)可以从其他类访问您的类的对象时,您需要为这些变量创建属性。

例如,如果我有名为"id"和"name"的私有变量但在某些情况下,类外部的读/写操作可能需要这个变量。在这种情况下,属性可以帮助我根据为属性定义的get/set来读取/写入该变量。属性可以是readonly/writeonly/readwrite两者。

这是演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Employee
{
    // Private Fields for Employee
    private int id;
    private string name;

    //Property for id variable/field
    public int EmployeeId
    {
       get
       {
          return id;
       }
       set
       {
          id = value;
       }
    }

    //Property for name variable/field
    public string EmployeeName
    {
       get
       {
          return name;
       }
       set
       {
          name = value;
       }
   }
}

class MyMain
{
    public static void Main(string [] args)
    {
       Employee aEmployee = new Employee();
       aEmployee.EmployeeId = 101;
       aEmployee.EmployeeName ="Sundaran S";
    }
}

这里的第二个问题是,"什么时候应该使用字段而不是属性?",在另一个答案中只作了简短的介绍,也有点像这个答案,但没有太多细节。

一般来说,所有其他的答案都是关于好的设计的:比起暴露领域,更喜欢暴露特性。虽然你可能不会经常发现自己在说"哇,想象一下,如果我把它变成一块地而不是一块地产,情况会变得多么糟糕",但想到这样一种情况,你会说"哇,感谢上帝,我在这里用了一块地而不是一块地产",就更难想象了。

但是字段相对于属性有一个优势,那就是它们可以用作"ref"/"out"参数。假设您有一个具有以下签名的方法:

1
public void TransformPoint(ref double x, ref double y);

假设您希望使用该方法来转换这样创建的数组:

1
2
System.Windows.Point[] points = new Point[1000000];
Initialize(points);

这是我认为最快的方法,因为x和y是属性:

1
2
3
4
5
6
7
8
for (int i = 0; i < points.Length; i++)
{
    double x = points[i].X;
    double y = points[i].Y;
    TransformPoint(ref x, ref y);
    points[i].X = x;
    points[i].Y = y;
}

那就太好了!除非你的测量结果证明了这一点,否则没有理由发臭。但我相信技术上并不能保证它能像这样快:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
internal struct MyPoint
{
    internal double X;
    internal double Y;
}

// ...

MyPoint[] points = new MyPoint[1000000];
Initialize(points);

// ...

for (int i = 0; i < points.Length; i++)
{
    TransformPoint(ref points[i].X, ref points[i].Y);
}

自己进行一些测量时,带有字段的版本大约需要61%的时间作为具有属性的版本(NET 4.6、Windows 7、x64、发布模式、未附加调试程序)。TransformPoint方法越昂贵,差异就越不明显。若要自己重复此操作,请运行时将第一行注释掉,而不将其注释掉。

即使上述方法没有性能优势,也有其他地方可以使用ref和out参数,例如在调用互锁或易失性方法家族时。注意:如果这对您来说是新的,volatile基本上是一种获得与volatile关键字提供的相同行为的方法。因此,像volatile一样,它并不能像它的名字所暗示的那样神奇地解决所有线程安全问题。

我绝对不想让自己看起来像是在鼓吹你去"哦,我应该开始公开字段而不是属性。"重点是,如果你需要经常在调用中使用这些成员来获取"ref"或"out"参数,特别是在一些可能是简单值类型的东西上,它不太可能需要任何增值元素。对于属性,可以进行参数设置。


如果要使用线程原语,则必须使用字段。属性可以破坏线程代码。除此之外,科里所说的是正确的。


此外,属性允许您在设置值时使用逻辑。

因此,可以说,如果值大于x,则只希望将值设置为整型字段,否则将引发异常。

非常有用的功能。


(这应该是一个评论,但我不能发表评论,所以如果不适合作为一个帖子,请原谅)。

我曾经在一个地方工作过,推荐的做法是使用公共字段而不是属性,而等效的属性def只会访问一个字段,如:

1
2
get { return _afield; }
set { _afield = value; }

他们的理由是,如果将来需要的话,公共领域可以转换成一个财产。当时我觉得有点奇怪。从这些帖子来看,这里似乎也没有多少人同意。你可能说过什么来尝试改变事情?

编辑:我应该补充一下,这个地方的所有代码库都是同时编译的,所以他们可能认为更改类的公共接口(通过将公共字段更改为属性)不是问题。


从技术上讲,我不认为有什么区别,因为属性只是由用户创建或由编译器自动创建的字段的包装。属性的目的是强制封装并提供轻量级的方法,比如特性。将字段声明为公共字段只是一种糟糕的做法,但它没有任何问题。


此页在msdn上有一个比较和提示,当:

https://msdn.microsoft.com/en-us/library/9d65as2e(v=vs.90).aspx


字段是类的普通成员变量或成员实例。属性是获取和设置其值的抽象。属性也被称为访问器,因为如果您将类中的字段公开为private,它们提供了一种更改和检索字段的方法。通常,您应该将成员变量声明为私有,然后为它们声明或定义属性。

1
2
3
4
5
6
7
8
  class SomeClass
  {
     int numbera; //Field

     //Property
    public static int numbera { get; set;}

  }

我对一个字段的设计是,一个字段只需要由它的父级修改,因此类。结果变量变为私有变量,然后能够授予在我只使用get的情况下读取属性系统外部的类/方法的权限。然后,该字段由属性和只读检索!如果你想修改它,你必须通过方法(例如构造器),我发现正是由于这种方法使你变得安全,我们可以更好地控制我们的代码,因为我们是"法兰"。我们可以很好地把所有的事情都公开,所以每一个可能的情况,变量/方法/类的概念等等…在我看来,这只是对代码开发、维护的一种帮助。例如,如果一个人使用公共字段恢复代码,他可以做任何事情,因此,与目标、代码编写原因的逻辑相关的事情"不合逻辑"。这是我的观点。

当我使用经典模型private field/public readonly属性时,对于10个privates字段,我应该编写10个publics属性!代码可以非常大更快。我发现了私有setter,现在我只使用私有setter的公共属性。setter在后台创建一个私有字段。

所以我以前的经典编程风格是:

1
2
3
4
5
6
7
8
9
public class MyClass
{
 private int _id;
 public int ID { get { return _id; } }
 public MyClass(int id)
 {
  _id = id;
 }
}

我的新编程风格:

1
2
3
4
5
6
7
8
public class MyClass
{
 public int ID { get; private set; }
 public MyClass(int id)
 {
  ID = id;
 }
}


字段是类中的变量。字段是可以通过使用访问修饰符来封装的数据。

属性与字段类似,因为它们定义了状态和与对象关联的数据。

与字段不同,属性有一个特殊的语法来控制一个人如何读取数据和写入数据,这些语法称为get和set操作符。集合逻辑通常可用于进行验证。


传统上,私有字段是通过getter和setter方法设置的。为了减少代码,可以使用属性来设置字段。


当你有一个"汽车"课的时候。属性是颜色、形状..

其中as字段是在类范围内定义的变量。


来自维基百科——面向对象编程:

Object-oriented programming (OOP) is a programming paradigm based on the concept of"objects", which are data structures that contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods. (emphasis added)

属性实际上是对象行为的一部分,但其设计目的是为对象的消费者提供使用对象数据的错觉/抽象。


属性是一种特殊的类成员,在属性中我们使用预定义的set或get方法,它们使用访问器,通过访问器我们可以读取、写入或更改私有字段的值。

例如,让我们取一个名为Employee的类,它的名称、年龄和员工ID都是私有字段。我们不能从类外访问这些字段,但可以通过属性访问这些私有字段。

我们为什么要使用属性?

公开和公开类字段是有风险的,因为您将无法控制分配和返回的内容。

为了用一个例子清楚地理解这一点,让我们以一个具有ID、passmark、name的学生班为例。在这个例子中,公共领域的一些问题

  • ID不应该是-ve。
  • 名称不能设置为空
  • 通过标记应为只读。
  • 如果缺少学生姓名,则不应返回任何姓名。

为了消除这个问题,我们使用get和set方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// A simple example
public class student
{
    public int ID;
    public int passmark;
    public string name;
}

public class Program
{
    public static void Main(string[] args)
    {
       student s1 = new student();
       s1.ID = -101; // here ID can't be -ve
       s1.Name = null ; // here Name can't be null
    }
}

现在我们以get-and-set方法为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class student
{
    private int _ID;
    private int _passmark;
    private string_name ;
    // for id property
    public void SetID(int ID)
    {
        if(ID<=0)
        {
            throw new exception("student ID should be greater then 0");
        }
        this._ID = ID;
    }
    public int getID()
    {
        return_ID;
    }
}
public class programme
{
    public static void main()
    {
        student s1 = new student ();
        s1.SetID(101);
    }
    // Like this we also can use for Name property
    public void SetName(string Name)
    {
        if(string.IsNullOrEmpty(Name))
        {
            throw new exeception("name can not be null");
        }
        this._Name = Name;
    }
    public string GetName()
    {
        if( string.IsNullOrEmpty(This.Name))
        {
            return"No Name";
        }
        else
        {
            return this._name;
        }
    }
        // Like this we also can use for Passmark property
    public int Getpassmark()
    {
        return this._passmark;
    }
}


属性用于公开字段。它们使用访问器(set,get),通过它可以读取、写入或操作私有字段的值。

属性不命名存储位置。相反,它们有读、写或计算值的访问器。

使用属性,我们可以对字段上设置的数据类型设置验证。

例如,我们有一个私有的整型字段"年龄",我们应该允许正值,因为年龄不能为负。

我们可以使用getter和setter以及using属性两种方法来实现这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 Using Getter and Setter

    // field
    private int _age;

    // setter
    public void set(int age){
      if (age <=0)
       throw new Exception();

      this._age = age;
    }

    // getter
    public int get (){
      return this._age;
    }

 Now using property we can do the same thing. In the value is a key word

    private int _age;

    public int Age{
    get{
        return this._age;
    }

    set{
       if (value <= 0)
         throw new Exception()
       }
    }

如果在get和set访问器中没有逻辑,则可以使用自动实现的属性。

当use auto-implemented属性compiles创建一个私有的匿名字段时,该字段只能通过get和set访问器访问。

1
public int Age{get;set;}

抽象属性抽象类可以具有抽象属性,该属性应在派生类中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class Person
   {
      public abstract string Name
      {
         get;
         set;
      }
      public abstract int Age
      {
         get;
         set;
      }
   }

// overriden something like this
// Declare a Name property of type string:
  public override string Name
  {
     get
     {
        return name;
     }
     set
     {
        name = value;
     }
  }

我们可以私设财产在这种情况下,我们可以单独设置auto属性(在类中用设置)

1
2
3
4
public int MyProperty
{
    get; private set;
}

使用此代码也可以实现相同的效果。在这个属性集功能中不可用,因为我们必须直接将值设置为字段。

1
2
3
4
5
private int myProperty;
public int MyProperty
{
    get { return myProperty; }
}

属性封装字段,从而使您能够对要设置或检索的值执行其他处理。如果您不在字段值上进行任何预处理或后处理,则使用属性通常是多余的。


想想看:你有一个房间和一扇门可以进入这个房间。如果你想检查谁来了,保护你的房间,那么你应该使用房地产,否则他们不会是任何门,每个人都很容易进入,没有任何规定。

1
2
3
4
5
6
7
class Room {
   public string sectionOne;
   public string sectionTwo;
}

Room r = new Room();
r.sectionOne ="enter";

人们很容易进入第一区,没有任何检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Room
{
   private string sectionOne;
   private string sectionTwo;

   public string SectionOne
   {
      get
      {
        return sectionOne;
      }
      set
      {
        sectionOne = Check(value);
      }
   }
}

Room r = new Room();
r.SectionOne ="enter";

现在你检查了那个人,知道他是否有什么不好的地方。


绝大多数情况下,它将是您访问的属性名,而不是变量名(字段),原因是它在.NET中被认为是良好的做法,尤其是在C中,它可以保护类中的每一个数据块,无论是实例变量还是静态变量(类变量),因为它与班级。

使用相应的属性保护所有这些变量,这些属性允许您定义、设置和获取访问器,并在操作这些数据时执行验证等操作。

但在其他情况下,比如数学类(系统名称空间),类中内置了一些静态属性。其中之一是数学常数pi

马蒂皮

因为pi是一个定义良好的数据块,所以我们不需要有多个pi副本,它总是相同的值。因此,静态变量有时用于在类的对象之间共享数据,但在只需要一个数据副本的情况下,静态变量也常用于常量信息。


在我看来,属性只是我们以前使用的"setxxx()"getxxx()"函数/方法/接口对,但是它们更简洁和优雅。


其他信息:默认情况下,get和set访问器与属性本身一样可访问。通过对访问器应用更严格的访问修饰符,可以单独控制/限制访问器的可访问性(对于get和set)。

例子:

1
2
3
4
5
6
7
8
9
10
11
public string Name
{
    get
    {
        return name;
    }
    protected set
    {
        name = value;
    }
}

这里get仍然是公开访问的(因为属性是公共的),但是set是受保护的(一个更受限制的访问说明符)。