在C#中,是什么使字段与属性不同?何时应该使用字段而不是属性?
当前回答
这里清楚地解释了区别。然而,只是为了总结和强调:
字段封装在类内部以进行内部操作,而财产可用于将类公开给外部世界,以及共享链接中显示的其他内部操作。此外,如果您希望基于特定字段的值加载某些方法或用户控件,则属性将为您完成此操作:
例如:
您可以在asp.net页面中的用户控件下方运行,只需为aspx页面中控件的Id前缀赋值,如下所示:
useMeId.Id=5 ---call the property of user control "UseMe.ascx"
使用Me.ascx
<%@ Register Src=~/"UseMe.ascx" TagPrefix="uc" TagName="UseMe" %>
<uc:UseMe runat="Server" id="useMeId" />
UseMe.ascx.cs
private int currentId;
public int Id
{
get
{
return currentId;
}
set
{
currentId = value;
LoadInitialData(currentId);
}
}
Private void LoadinitialData(int currentIdParam)
{
//your action
}
其他回答
虽然字段和财产看起来很相似,但它们是两个完全不同的语言元素。
字段是在类级别上存储数据的唯一机制。字段在概念上是类范围内的变量。如果要将一些数据存储到类(对象)的实例中,则需要使用字段。别无选择。虽然财产无法存储任何数据,但看起来它们可以这样做。请参阅下文。另一方面,财产从不存储数据。它们只是一对方法(get和set),在语法上可以以与字段类似的方式调用,在大多数情况下,它们访问(用于读取或写入)字段,这是一些混淆的根源。但是,由于属性方法是(有一些限制,如固定原型)常规C#方法,它们可以做常规方法所能做的任何事情。这意味着它们可以有1000行代码,它们可以抛出异常,调用其他方法,甚至可以是虚拟的、抽象的或重写的。财产之所以特别,是因为C#编译器将一些额外的元数据存储到程序集中,这些程序集可用于搜索特定的财产,而这些属性是广泛使用的功能。
Get和set属性方法具有以下原型。
PROPERTY_TYPE get();
void set(PROPERTY_TYPE value);
因此,这意味着可以通过定义一个字段和两个相应的方法来“模拟”财产。
class PropertyEmulation
{
private string MSomeValue;
public string GetSomeValue()
{
return(MSomeValue);
}
public void SetSomeValue(string value)
{
MSomeValue=value;
}
}
这种属性模拟对于不支持财产的编程语言来说是很典型的,比如标准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).
尽管属性方法可以做任何事情,但在大多数情况下,它们充当了访问对象字段的一种方式。如果你想让一个字段可以被其他类访问,你可以通过两种方式来实现。
将字段设置为公共字段-不可取。使用财产。
这是一个使用公共字段的类。
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;
虽然从设计角度来看,代码是完全有效的,但它有几个缺点。因为字段既可以读也可以写,所以不能阻止用户写入字段。您可以应用readonly关键字,但通过这种方式,您只能在构造函数中初始化readonly字段。此外,没有什么可以阻止您将无效值存储到字段中。
name.FullName=null;
name.YearOfBirth=2200;
name.Age=-140;
代码有效,所有赋值都将执行,尽管它们不合逻辑。Age为负值,YearOfBirth是遥远的未来,与Age不对应,FullName为空。对于字段,您无法阻止类名的用户犯这样的错误。
下面是一个具有财产的代码,用于修复这些问题。
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());
}
}
}
类的更新版本具有以下优点。
检查FullName和YearOfBirth是否存在无效值。年龄不可写。它是根据出生年份和当前年份计算的。新属性FullNameInUppercase将FullName转换为UPPER CASE。这是一个有点做作的属性用法示例,其中财产通常用于以更适合用户的格式显示字段值,例如在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]);
因为C#3.0允许您定义自动财产。这是一个例子。
class AutoProps
{
public int Value1
{
get;
set;
}
public int Value2
{
get;
set;
}
}
尽管类AutoProps只包含财产(或看起来像),但它可以存储2个值,并且该类的对象大小等于sizeof(Value1)+size of(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;
}
}
}
因此,正如您所看到的,编译器仍然使用字段来存储值,因为字段是将值存储到对象中的唯一方法。
正如你所见,虽然财产和字段有相似的用法语法,但它们是非常不同的概念。即使您使用自动财产或事件,隐藏字段也由编译器生成,真实数据存储在这里。
如果需要使外部世界(类的用户)可以访问字段值,请不要使用公共或受保护的字段。字段始终应标记为私有。财产允许您进行值检查、格式化、转换等,通常会使代码更安全、更可读、更可扩展,以便将来进行修改。
由于他们中的许多人已经解释了财产和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;
}
}
这里清楚地解释了区别。然而,只是为了总结和强调:
字段封装在类内部以进行内部操作,而财产可用于将类公开给外部世界,以及共享链接中显示的其他内部操作。此外,如果您希望基于特定字段的值加载某些方法或用户控件,则属性将为您完成此操作:
例如:
您可以在asp.net页面中的用户控件下方运行,只需为aspx页面中控件的Id前缀赋值,如下所示:
useMeId.Id=5 ---call the property of user control "UseMe.ascx"
使用Me.ascx
<%@ Register Src=~/"UseMe.ascx" TagPrefix="uc" TagName="UseMe" %>
<uc:UseMe runat="Server" id="useMeId" />
UseMe.ascx.cs
private int currentId;
public int Id
{
get
{
return currentId;
}
set
{
currentId = value;
LoadInitialData(currentId);
}
}
Private void LoadinitialData(int currentIdParam)
{
//your action
}
财产显示字段。字段应该(几乎总是)对类保持私有,并通过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指出,您还可以在访问属性时执行其他逻辑,例如验证,这是另一个有用的功能。
字段是类中的变量。字段是可以通过使用访问修饰符封装的数据。
财产与字段类似,它们定义与对象关联的状态和数据。
与字段不同,属性有一种特殊的语法来控制用户如何读取数据和写入数据,这些被称为get和set运算符。集合逻辑通常可用于进行验证。