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


当前回答

由于他们中的许多人已经解释了财产和Field的技术优缺点,现在是时候进入实时示例了。

1.财产允许您设置只读访问级别

考虑dataTable.Rows.Count和dataTable.Column[i].Caption的情况。它们来自dataTable类,对我们来说都是公共的。对它们的访问级别的不同之处在于,我们不能将值设置为dataTable.Rrows.Count,但我们可以读写dataTable.Colomn[i].Taption。这可以通过Field实现吗?不这只能通过财产完成。

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中,如Text、Name等。当我们拖放按钮时,当我们单击财产时,它将自动找到类button并过滤财产,并在Property Grid中显示该类(其中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中,将显示财产Name和Text,但不显示SomeProperty。为什么?因为财产可以接受属性。如果[可浏览(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不接受字段。为此,我们应该使用财产。

5.调试模式

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

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

其他回答

财产显示字段。字段应该(几乎总是)对类保持私有,并通过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指出,您还可以在访问属性时执行其他逻辑,例如验证,这是另一个有用的功能。

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

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

但在其他情况下,如Math类(System命名空间),类中内置了一些静态财产。其中一个是数学常数PI

例如数学PI

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

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

总的来说,所有其他答案都是关于良好设计的一针见血的:比起暴露字段,更喜欢暴露财产。虽然你可能不会经常说“哇,想象一下,如果我把这里变成了一块地而不是一块地,情况会变得多么糟糕”,但想到你会说“哇!谢天谢地,我在这里用了一块土地而不是一处地”的情况,那就难得多了

但与财产相比,字段有一个优点,那就是它们可以用作“ref”/“out”参数。假设您有一个具有以下签名的方法:

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

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

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

因为X和Y是财产,所以我认为这是最快的方法:

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

我自己进行了一些测量,带字段的版本与带财产的版本(.NET 4.6、Windows 7、x64、发布模式、未附加调试程序)相比,大约需要61%的时间。TransformPoint方法的成本越高,差异就越不明显。若要自己重复此操作,请将第一行注释掉,并将其未注释掉。

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

我当然不想让人觉得我在提倡你说“哦,我应该开始公开字段,而不是财产。”关键是,如果你需要在调用中定期使用这些成员来接受“ref”或“out”参数,尤其是在可能是简单值类型的东西上,它不太可能需要财产的任何增值元素,那么可以进行一个参数。

基本区别和一般区别是:

领域

始终给予get和set访问权限不会造成副作用(引发异常、调用方法、更改字段(获取/设置的字段除外)等)

财产

并非总是同时提供get和set访问权限CAN导致副作用

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

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

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

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

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

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