在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指出,当访问属性时,您还可以执行其他逻辑,例如验证,这是另一个有用的功能.
面向对象的编程原则说,类的内部工作应该隐藏在外部世界之外.如果您公开一个字段,那么您实际上是暴露了该类的内部实现.因此,我们使用Properties(或Java的方法)来包装字段,以使我们能够在不破坏代码的情况下更改实现,具体取决于我们.看到我们可以在属性中放置逻辑也允许我们在需要时执行验证逻辑等.C#3有可能令人困惑的autoproperties概念.这允许我们简单地定义属性,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标志不会被触发,因此在决定需要返回数据库时,保存功能将跳过它.
使用Properties,可以在更改属性的值(也就是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); } } }
因为他们中许多人与技术的利弊解释Properties
和Field
,是时候进入实时的例子.
1.属性允许您设置只读访问级别
考虑的情况下dataTable.Rows.Count
和dataTable.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中的属性
您可能Button
在Visual Studio中使用过.它的属性显示在PropertyGrid
类似Text
,Name
等等.当我们拖放一个按钮,当我们点击属性,它会自动查找类Button
和过滤器Properties
和显示,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
,属性Name
和Text
将显示,但不会SomeProperty
.为什么???因为属性可以接受属性.如果[Browsable(false)]
错误则不显示.
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.只能在绑定源中使用属性
绑定源有助于我们减少代码行数.Fields
不被接受BindingSource
.我们应该用Properties
它.
5.调试模式
考虑一下我们Field
用来持有价值.在某些时候,我们需要调试并检查该字段的值为null的位置.在代码行数超过1000的情况下很难做到.在这种情况下我们可以使用Property
并可以在里面设置调试模式Property
.
public string Name { // Can set debug mode inside get or set get { return _name; } set { _name = value; } }
甲字段是直接宣布类或结构的一个变量.类或结构可以包含实例字段或静态字段或两者.通常,您应该仅将字段用于具有私有或受保护可访问性的变量.应通过方法,属性和索引器提供类暴露给客户端代码的数据.通过使用这些构造来间接访问内部字段,可以防止无效的输入值.
甲属性是,提供了一个灵活的机制来读,写,或计算私有字段的值的构件.属性可以像它们是公共数据成员一样使用,但它们实际上是称为访问器的特殊方法.这样可以轻松访问数据,并且仍然有助于提高方法的安全性和灵活性.属性使类能够公开获取和设置值的公共方式,同时隐藏实现或验证代码.get属性访问器用于返回属性值,set访问器用于分配新值.
属性的主要优点是允许您更改对象上的数据访问方式,而不会破坏它的公共接口.例如,如果您需要添加额外的验证,或者将存储的字段更改为计算结果,则可以在最初将该字段作为属性公开时轻松完成.如果您只是直接暴露了一个字段,那么您必须更改类的公共接口以添加新功能.该更改将破坏现有客户端,要求在他们使用新版本的代码之前重新编译它们.
如果你编写一个专为广泛使用而设计的类库(比如数百万人使用的.NET Framework),这可能是一个问题.但是,如果你在一个小代码库内写一个内部使用的类(比如<= 50 K行),这真的不是什么大问题,因为没有人会受到你的更改的不利影响.在这种情况下,它实际上只取决于个人偏好.
在后台,属性被编译为方法.所以一个Name
属性被编译成get_Name()
和set_Name(string value)
.如果您研究编译的代码,您可以看到这一点.因此,使用它们时会产生(非常)小的性能开销.通常,如果将字段暴露给外部,您将始终使用属性,如果需要对值进行验证,则通常会在内部使用该属性.
属性支持非对称访问,即您可以使用getter和setter,也可以只使用其中一个.类似地,属性支持getter/setter的个人可访问性.字段始终是对称的,即您始终可以获取和设置值.对此的例外是readonly字段,显然在初始化后无法设置.
属性可能会运行很长时间,有副作用,甚至可能抛出异常.字段很快,没有副作用,永远不会抛出异常.由于副作用,属性可能会为每个调用返回不同的值(可能是DateTime.Now的情况,即DateTime.Now并不总是等于DateTime.Now).字段始终返回相同的值.
字段可以用于out/ref参数,属性可以不用.属性支持额外的逻辑 - 这可以用于实现延迟加载等.
属性通过封装获取/设置值的任何方式来支持抽象级别.
在大多数/所有情况下使用属性,但尽量避免副作用.
如果希望私有变量(字段)可以从其他类访问类的对象,则需要为这些变量创建属性.
例如,如果我有名为"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"; } }
这里的第二个问题,“当应在现场使用,而不是财产?”,仅在简要介绍了这对方的回答还挺这一个了,但没有真正的细节。
一般而言,所有其他答案都是关于良好设计的问题:相对于公开字段,更喜欢公开属性。虽然你可能不会经常发现自己说“哇,想象有多少糟糕的事情是,如果我做了,而不是财产这个领域”,这是这么多更为罕见想到的情况下,你会说:“哇,感谢上帝,我在这里使用的是田地,而不是财产。”
但是,字段具有优于属性的一个优点,那就是它们可以用作“ 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”参数的调用中定期使用这些成员,尤其是在那些可能是简单值类型,不太可能不需要属性的任何增值元素的调用上,可以争论。