Cor*_*ory 890
属性公开字段.字段应该(几乎总是)保持对类的私有,并通过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;}
}
Run Code Online (Sandbox Code Playgroud)
@Kent指出,属性不需要封装字段,它们可以在其他字段上进行计算,或用于其他目的.
@GSS指出,当访问属性时,您还可以执行其他逻辑,例如验证,这是另一个有用的功能.
dan*_*ain 244
面向对象的编程原则说,类的内部工作应该隐藏在外部世界之外.如果您公开一个字段,那么您实际上是暴露了该类的内部实现.因此,我们使用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
}
Run Code Online (Sandbox Code Playgroud)
Han*_*ken 160
一个重要的区别是接口可以具有属性但不具有字段.对我来说,这强调了属性应该用于定义类的公共接口,而字段用于在类的私有内部工作中使用.作为一项规则,我很少创建公共字段,同样我很少创建非公共属性.
Chr*_*ris 95
我将举几个使用可能使齿轮转动的属性的例子:
Jeh*_*hof 49
使用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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Sar*_*avu 43
因为他们中许多人与技术的利弊解释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;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
在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;
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
小智 31
甲字段是直接宣布类或结构的一个变量.类或结构可以包含实例字段或静态字段或两者.通常,您应该仅将字段用于具有私有或受保护可访问性的变量.应通过方法,属性和索引器提供类暴露给客户端代码的数据.通过使用这些构造来间接访问内部字段,可以防止无效的输入值.
甲属性是,提供了一个灵活的机制来读,写,或计算私有字段的值的构件.属性可以像它们是公共数据成员一样使用,但它们实际上是称为访问器的特殊方法.这样可以轻松访问数据,并且仍然有助于提高方法的安全性和灵活性.属性使类能够公开获取和设置值的公共方式,同时隐藏实现或验证代码.get属性访问器用于返回属性值,set访问器用于分配新值.
Tim*_*y_A 13
尽管字段和属性看起来彼此相似,但它们是两种完全不同的语言元素。
字段是如何在类级别存储数据的唯一机制。字段在概念上是类范围内的变量。如果要将某些数据存储到类(对象)的实例中,则需要使用字段。没有其他选择。即使属性不能存储任何数据,但看起来他们能够这样做。见下文。
另一方面,属性从不存储数据。它们只是成对的方法(get 和 set),它们可以在语法上以与字段类似的方式调用,并且在大多数情况下它们访问(读取或写入)字段,这是一些混淆的根源。但是因为属性方法是(有一些限制,比如固定原型)常规的 C# 方法,它们可以做任何常规方法可以做的事情。这意味着它们可以有 1000 行代码,可以抛出异常,调用其他方法,甚至可以是虚拟的、抽象的或覆盖的。属性的特殊之处在于,C# 编译器将一些额外的元数据存储到可用于搜索特定属性的程序集中——广泛使用的功能。
Get 和 set 属性方法具有以下原型。
PROPERTY_TYPE get();
void set(PROPERTY_TYPE value);
Run Code Online (Sandbox Code Playgroud)
所以这意味着可以通过定义一个字段和 2 个相应的方法来“模拟”属性。
class PropertyEmulation
{
private string MSomeValue;
public string GetSomeValue()
{
return(MSomeValue);
}
public void SetSomeValue(string value)
{
MSomeValue=value;
}
}
Run Code Online (Sandbox Code Playgroud)
这种属性模拟对于不支持属性的编程语言来说是典型的——比如标准 C++。在 C# 中,您应该始终更喜欢使用属性作为访问字段的方式。
因为只有字段才能存储数据,意味着类包含的字段越多,该类的内存对象就会消耗越多。另一方面,向类中添加新属性不会使此类的对象变大。这是示例。
class OneHundredFields
{
public int Field1;
public int Field2;
...
public int Field100;
}
OneHundredFields Instance=new OneHundredFields() // Variable 'Instance' consumes 100*sizeof(int) bytes of memory.
class OneHundredProperties
{
public int Property1
{
get
{
return(1000);
}
set
{
// Empty.
}
}
public int Property2
{
get
{
return(1000);
}
set
{
// Empty.
}
}
...
public int Property100
{
get
{
return(1000);
}
set
{
// Empty.
}
}
}
OneHundredProperties Instance=new OneHundredProperties() // !!!!! Variable 'Instance' consumes 0 bytes of memory. (In fact a some bytes are consumed becasue every object contais some auxiliarity data, but size doesn't depend on number of properties).
Run Code Online (Sandbox Code Playgroud)
尽管属性方法可以做任何事情,但在大多数情况下,它们用作访问对象字段的一种方式。如果您想让其他类可以访问某个字段,您可以通过两种方式进行。
这是一个使用公共字段的类。
class Name
{
public string FullName;
public int YearOfBirth;
public int Age;
}
Name name=new Name();
name.FullName="Tim Anderson";
name.YearOfBirth=1979;
name.Age=40;
Run Code Online (Sandbox Code Playgroud)
虽然代码完全有效,但从设计的角度来看,它有几个缺点。由于字段既可以读取也可以写入,因此您无法阻止用户写入字段。您可以应用readonly
关键字,但这样,您必须仅在构造函数中初始化只读字段。更重要的是,没有什么可以阻止您将无效值存储到您的字段中。
name.FullName=null;
name.YearOfBirth=2200;
name.Age=-140;
Run Code Online (Sandbox Code Playgroud)
代码是有效的,所有的赋值都会被执行,尽管它们是不合逻辑的。Age
有一个负值,YearOfBirth
在未来很远,不对应于年龄,FullName
为空。对于字段,您无法阻止用户class Name
犯此类错误。
这是具有修复这些问题的属性的代码。
class Name
{
private string MFullName="";
private int MYearOfBirth;
public string FullName
{
get
{
return(MFullName);
}
set
{
if (value==null)
{
throw(new InvalidOperationException("Error !"));
}
MFullName=value;
}
}
public int YearOfBirth
{
get
{
return(MYearOfBirth);
}
set
{
if (MYearOfBirth<1900 || MYearOfBirth>DateTime.Now.Year)
{
throw(new InvalidOperationException("Error !"));
}
MYearOfBirth=value;
}
}
public int Age
{
get
{
return(DateTime.Now.Year-MYearOfBirth);
}
}
public string FullNameInUppercase
{
get
{
return(MFullName.ToUpper());
}
}
}
Run Code Online (Sandbox Code Playgroud)
类的更新版本具有以下优点。
FullName
并YearOfBirth
检查无效值。Age
不可写。它是从YearOfBirth
当年开始计算的。FullNameInUppercase
转换FullName
为大写。这是一个人为的属性使用示例,其中属性通常用于以更适合用户的格式显示字段值 - 例如,在特定的DateTime
格式数字上使用当前区域设置。除此之外,属性可以定义为虚拟的或覆盖的——仅仅因为它们是常规的 .NET 方法。与常规方法相同的规则适用于此类属性方法。
C# 还支持索引器,它们是在属性方法中具有索引参数的属性。这是示例。
class MyList
{
private string[] MBuffer;
public MyList()
{
MBuffer=new string[100];
}
public string this[int Index]
{
get
{
return(MBuffer[Index]);
}
set
{
MBuffer[Index]=value;
}
}
}
MyList List=new MyList();
List[10]="ABC";
Console.WriteLine(List[10]);
Run Code Online (Sandbox Code Playgroud)
由于 C# 3.0 允许您定义自动属性。这是示例。
class AutoProps
{
public int Value1
{
get;
set;
}
public int Value2
{
get;
set;
}
}
Run Code Online (Sandbox Code Playgroud)
即使class AutoProps
只包含属性(或看起来像),它也可以存储 2 个值,此类对象的大小等于sizeof(Value1)+sizeof(Value2)
=4+4=8 个字节。
这样做的原因是简单的。定义自动属性时,C# 编译器会生成自动代码,其中包含隐藏字段和具有访问此隐藏字段的属性方法的属性。这是编译器生成的代码。
这是ILSpy从编译后的程序集中生成的代码。类包含生成的隐藏字段和属性。
internal class AutoProps
{
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private int <Value1>k__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private int <Value2>k__BackingField;
public int Value1
{
[CompilerGenerated]
get
{
return <Value1>k__BackingField;
}
[CompilerGenerated]
set
{
<Value1>k__BackingField = value;
}
}
public int Value2
{
[CompilerGenerated]
get
{
return <Value2>k__BackingField;
}
[CompilerGenerated]
set
{
<Value2>k__BackingField = value;
}
}
}
Run Code Online (Sandbox Code Playgroud)
因此,如您所见,编译器仍然使用字段来存储值——因为字段是将值存储到对象中的唯一方法。
如您所见,尽管属性和字段具有相似的用法语法,但它们是非常不同的概念。即使您使用自动属性或事件 - 隐藏字段也是由存储真实数据的编译器生成的。
如果您需要使外部世界(您班级的用户)可以访问字段值,请不要使用公共或受保护的字段。字段应始终标记为私有。属性允许您进行值检查、格式化、转换等,并且通常使您的代码更安全、更易读和更可扩展以供将来修改。
Sco*_*ski 12
属性的主要优点是允许您更改对象上的数据访问方式,而不会破坏它的公共接口.例如,如果您需要添加额外的验证,或者将存储的字段更改为计算结果,则可以在最初将该字段作为属性公开时轻松完成.如果您只是直接暴露了一个字段,那么您必须更改类的公共接口以添加新功能.该更改将破坏现有客户端,要求在他们使用新版本的代码之前重新编译它们.
如果你编写一个专为广泛使用而设计的类库(比如数百万人使用的.NET Framework),这可能是一个问题.但是,如果你在一个小代码库内写一个内部使用的类(比如<= 50 K行),这真的不是什么大问题,因为没有人会受到你的更改的不利影响.在这种情况下,它实际上只取决于个人偏好.
Run*_*tad 11
在后台,属性被编译为方法.所以一个Name
属性被编译成get_Name()
和set_Name(string value)
.如果您研究编译的代码,您可以看到这一点.因此,使用它们时会产生(非常)小的性能开销.通常,如果将字段暴露给外部,您将始终使用属性,如果需要对值进行验证,则通常会在内部使用该属性.
Bri*_*sen 10
属性支持非对称访问,即您可以使用getter和setter,也可以只使用其中一个.类似地,属性支持getter/setter的个人可访问性.字段始终是对称的,即您始终可以获取和设置值.对此的例外是readonly字段,显然在初始化后无法设置.
属性可能会运行很长时间,有副作用,甚至可能抛出异常.字段很快,没有副作用,永远不会抛出异常.由于副作用,属性可能会为每个调用返回不同的值(可能是DateTime.Now的情况,即DateTime.Now并不总是等于DateTime.Now).字段始终返回相同的值.
字段可以用于out/ref参数,属性可以不用.属性支持额外的逻辑 - 这可以用于实现延迟加载等.
属性通过封装获取/设置值的任何方式来支持抽象级别.
在大多数/所有情况下使用属性,但尽量避免副作用.
小智 7
如果希望私有变量(字段)可以从其他类访问类的对象,则需要为这些变量创建属性.
例如,如果我有名为"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";
}
}
Run Code Online (Sandbox Code Playgroud)
这里的第二个问题,“当应在现场使用,而不是财产?”,仅在简要介绍了这对方的回答还挺这一个了,但没有真正的细节。
一般而言,所有其他答案都是关于良好设计的问题:相对于公开字段,更喜欢公开属性。虽然你可能不会经常发现自己说“哇,想象有多少糟糕的事情是,如果我做了,而不是财产这个领域”,这是这么多更为罕见想到的情况下,你会说:“哇,感谢上帝,我在这里使用的是田地,而不是财产。”
但是,字段具有优于属性的一个优点,那就是它们可以用作“ ref” /“ out”参数。假设您有一个具有以下签名的方法:
public void TransformPoint(ref double x, ref double y);
Run Code Online (Sandbox Code Playgroud)
并假设您想使用该方法来转换这样创建的数组:
System.Windows.Point[] points = new Point[1000000];
Initialize(points);
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
那将是相当不错的!除非您有其他证明的测量方法,否则没有理由发臭。但我相信,从技术上讲,它并不能保证达到以下速度:
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);
}
Run Code Online (Sandbox Code Playgroud)
自己进行一些测量,带有字段的版本大约要花费带有属性的版本(.NET 4.6,Windows 7,x64,发布模式,未连接调试器)的61%的时间。该TransformPoint
方法越昂贵,差异就越不明显。要自己重复一遍,请先注释掉第一行,然后注释掉第一行。
即使上面没有任何性能上的好处,在其他地方也可以使用ref和out参数可能是有益的,例如在调用Interlocked或Volatile系列方法时。 注意:如果这是您的新手,那么Volatile本质上是一种实现volatile
关键字提供的相同行为的方法。因此,就像一样volatile
,它不能神奇地解决所有线程安全问题,就像它的名字暗示的那样。
我绝对不希望我提倡您去“哦,我应该开始显示字段而不是属性”。关键是,如果您需要在带有“ ref”或“ out”参数的调用中定期使用这些成员,尤其是在那些可能是简单值类型,不太可能不需要属性的任何增值元素的调用上,可以争论。
(这确实应该是一个评论,但我不能发表评论,所以如果它不适合作为帖子,请原谅)。
我曾经在一个地方工作,建议的做法是使用公共字段而不是属性,而等效的属性 def 只是访问字段,如下所示:
get { return _afield; }
set { _afield = value; }
Run Code Online (Sandbox Code Playgroud)
他们的理由是,如果需要的话,公共领域可以在未来转化为财产。当时我觉得有点奇怪。从这些帖子来看,似乎也没有多少人同意。你可能会说些什么来尝试改变事情?
编辑:我应该补充一点,这个地方的所有代码库都是同时编译的,因此他们可能认为更改类的公共接口(通过将公共字段更改为属性)不是问题。
从技术上讲,我认为没有区别,因为属性只是用户创建的字段或编译器自动创建的字段的包装器。属性的目的是强制封装并提供类似轻量级方法的功能。将字段声明为公共字段只是一种不好的做法,但它没有任何问题。
字段是普通的成员变量或类的成员实例。属性是获取和设置其值的抽象。属性也称为访问器,因为如果您将类中的字段公开为私有字段,它们提供了一种更改和检索字段的方法。一般来说,您应该将成员变量声明为私有,然后为它们声明或定义属性。
class SomeClass
{
int numbera; //Field
//Property
public static int numbera { get; set;}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
416191 次 |
最近记录: |