提问



在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指出,属性不需要封装字段,它们可以在其他字段上进行计算,或用于其他目的。


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

其它参考1


面向对象的编程原则说,一个类的内部工作应该隐藏在外部世界之外。如果你暴露一个字段,你实际上暴露了类的内部实现。因此我们使用Properties(或Java的方法)包装字段,使我们能够在不破坏代码的情况下更改实现,具体取决于我们。看到我们可以在属性中放置逻辑也允许我们在需要时执行验证逻辑等。
C#3有可能令人困惑的autoproperties概念。这允许我们简单地定义Property,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
}

其它参考2


一个重要的区别是接口可以具有属性但不具有字段。对我来说,这强调应该使用属性来定义类的公共接口,而字段意味着在类的私有内部工作中使用。通常我很少创建公共字段,同样我很少创建非 - 公共财产。

其它参考3


我将给你一些使用可能使齿轮转动的属性的例子:



  • 延迟初始化:如果你有一个对象的属性加载很昂贵,但在正常的代码运行中没有被访问,你可以通过属性。这样,它只是坐在那里,但是第一次另一个模块试图调用该属性时,它会检查底层字段是否为空 - 如果是,它继续并加载它,调用模块不知道。这可以大大加快了对象的初始化。

  • Dirty Tracking:我在StackOverflow上从我自己的问题中学到了什么。当我有很多对象在运行期间可能已更改的值时,我可以使用该属性来跟踪是否需要将它们保存回数据库。如果没有对象的单个属性发生更改,则IsDirty标志不会被触发,因此在决定需要返回数据库时,保存功能将跳过它。


其它参考4


使用Properties,可以在更改属性值(也就是PropertyChangedEvent)时或在更改值以支持取消之前抛出事件。 [63]


对于(直接访问)字段,这是不可能的。


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);
   }
 }
}

其它参考5


由于他们中的许多人已经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赢得t显示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。为什么???因为属性可以接受属性。如果[Browsable(false)]为假,则不显示。[65]


第3。可以在Properties 中执行语句


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[66]


5。调试模式


考虑我们使用Field来保存一个值。在某些时候,我们需要调试并检查该字段的值为null的位置。在代码行数超过1000的情况下很难做到。在这种情况下我们可以使用Property并在Property中设置调试模式。


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

其它参考6


差异 - 使用(何时以及为何)



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


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

其它参考7


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


如果你编写一个专为广泛使用而设计的类库(比如数百万人使用的.NET Framework),这可能是一个问题。但是,如果你在一个小代码库内写一个内部使用的类(比如<=50 K行),这真的不是什么大问题,因为没有人会受到你的改变的不利影响。在这种情况下,它真的只是归结为个人偏好。

其它参考8


在后台,属性被编译为方法。所以Name属性被编译成get_Name()set_Name(string value)。如果您研究已编译的代码,您可以看到这一点。
因此,使用它们时会产生(非常)小的性能开销。通常,如果将字段暴露给外部,则始终使用属性,如果需要对值进行验证,则通常会在内部使用该属性。

其它参考9


属性支持非对称访问,即您可以使用getter和setter,也可以只使用其中一个。类似地,属性支持getter/setter的单个可访问性。字段始终是对称的,即您始终可以获取和设置值。对此的例外是readonly字段,显然在初始化后无法设置。


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


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


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


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

其它参考10


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


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


这是演示


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";
    }
}

其它参考11


这里的第二个问题,何时应该使用一个字段而不是一个属性?,在这个其他答案中只是简单地触及了这个问题,但也不是很详细。


一般来说,所有其他答案都是关于良好设计的重点:更喜欢在暴露字段上暴露属性。虽然你可能会经常经常发现自己在说哇,但想象一下,如果我把这个字段变成一个字段而不是一个属性会有多糟糕,那么那么多更难得想到你会说哇,感谢上帝,我在这里使用了一块土地而不是财产。


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


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


并假设您要使用该方法转换如下所示的数组:


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


在这里,我认为最快的方法,因为X和Y是属性:[69] [70]


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;
}


这将是非常好的!除非你有测量结果证明不是这样,否则没有理由抛出臭味。但我认为技术上保证不会像这样快:


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方法越昂贵,差异变得越不明显。要自己重复这一点,请在第一行注释掉并且没有注释掉。[71]


即使上面没有性能优势,也有其他地方能够使用ref和out参数可能是有益的,例如在调用Interlocked或Volatile系列方法时。 注意:如果这对您来说是新的,Volatile基本上是一种获得volatile关键字提供的相同行为的方法。因此,像volatile一样,它并没有神奇地解决所有线程安全问题,就像它的名字所暗示的那样。 [72] [73]


我绝对不希望看起来像是在鼓吹你说哦,我应该开始暴露场而不是属性。关键在于,如果你需要经常在带有ref或out参数的调用中使用这些成员,特别是那些可能是一个简单的值类型的东西,它不太可能需要任何增值元素。属性,可以进行论证。

其它参考12


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


所以你可以说你只想设置一个整数字段的值,如果值大于x,否则抛出异常。


真有用的功能。

其它参考13


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

其它参考14


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


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


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


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


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

其它参考15


MSDN上的这个页面有一个比较和提示,在以下情况下使用哪一个:


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

其它参考16


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


  class SomeClass
  {
     int numbera; //Field

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

  }

其它参考17


属性封装字段,因此您可以对要设置或检索的值执行其他处理。如果您不对字段值进行任何预处理或后处理,则使用属性通常会过度。

其它参考18


从技术上讲,我不认为存在差异,因为属性只是由用户创建或由编译器自动创建的字段的包装。属性的目的是强制封装并提供类似于方法的轻量级功能。
将字段声明为公共字段只是一种不好的做法,但它没有任何问题。

其它参考19


IMO,属性只是我们之前使用的SetXXX()GetXXX()函数/方法/接口对,但它们更简洁和优雅。

其它参考20


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

其它参考21


当你有一个汽车的班级。属性是颜色,形状..


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

其它参考22


来自维基百科 - 面向对象的编程:[75]



  面向对象编程(OOP)是一种基于对象概念的编程范例,对象是包含数据的数据结构,以字段的形式,通常称为属性;和代码,以程序的形式,通常称为方法。 (强调添加)



属性实际上是对象行为的一部分,但旨在为对象的使用者提供使用对象数据的幻觉/抽象。

其它参考23


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


例如,让我们取一个名为Employee的类,其中包含name,age和Employee_Id的私有字段。我们无法从类外部访问这些字段,但我们可以通过属性访问这些私有字段。


我们为什么要使用属性?


将课程领域公之于众暴露它是有风险的,因为你无法控制被分配的东西和回。


为了通过一个例子清楚地理解这一点,我们可以选择一个有ID,密码,姓名的学生班。现在在这个例子中公共字段存在一些问题



  • ID不应该是-ve。

  • 名称不能设为null

  • 通行证应该是只读的。

  • 如果缺少学生姓名,则不应返回姓名。



要删除此问题,我们使用Get和set方法。


// 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和set方法的例子


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;
    }
}

其它参考24


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


当我使用经典模型私有领域/公共只读属性时,对于10个私有领域,我应该写10个公共属性!代码可以更快。我发现私人二传手,现在我只使用私人二传手的公共财产。
setter在后台创建一个私有字段。


这就是为什么我的旧经典编程风格是:


public class MyClass
{
 private int _id;
 public int ID { get { return _id; } }
 public MyClass(int id)
 {
  _id = id;
 }
}


我的新编程风格:


public class MyClass
{
 public int ID { get; private set; }
 public MyClass(int id)
 {
  ID = id;
 }
}

其它参考25


绝大多数情况下,它将成为您访问的属性名称而不是变量名称(字段)其原因是它被认为是.NET和C#中的良好实践特别是保护类中的每一段数据,无论它是实例变量还是静态变量(类变量),因为它与类相关联。


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


但在其他情况下,如Math类(系统命名空间),有几个静态属性构建在类中。其中一个是数学常数PI [77] [78]


例如。 Math.PI


因为PI是一个定义明确的数据,我们不需要拥有PI的多个副本,它总是具有相同的值。所以静态变量有时用于在类的对象之间共享数据,但它们也常用于常量信息,您只需要一份数据的副本。

其它参考26


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


例:


public string Name
{
    get
    {
        return name;
    }
    protected set
    {
        name = value;
    }
}


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

其它参考27


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


属性未命名存储位置。相反,它们具有读取,写入或计算其值的访问器。


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


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


我们可以使用getter和setter以及使用属性以两种方式执行此操作。


 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()
       }
    }


Auto Implemented属性如果我们在获取和设置访问器中没有逻辑,我们可以使用自动实现的属性。


当你自动实现的属性编译创建一个只能通过get和set访问器访问的私有匿名字段


public int Age{get;set;}


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


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属性(在类中设置)


public int MyProperty
{
    get; private set;
}


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


private int myProperty;
public int MyProperty
{
    get { return myProperty; }
}

其它参考28


想想ab出来:你有一个房间和一扇门进入这个房间。如果你想检查谁进入并保护你的房间,那么你应该使用属性,否则他们不会成为任何门,每个人都很容易进入任何规则


class Room {
   public string sectionOne;
   public string sectionTwo;
}

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


人们很容易进入sectionOne,没有任何检查


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";


现在你检查了这个人并且知道他是否有邪恶的东西