在C#中,是什么使字段与属性不同?何时应该使用字段而不是属性?
当前回答
在后台,属性被编译为方法。因此,Name属性被编译为get_Name()和set_Name(字符串值)。如果您研究编译的代码,您可以看到这一点。因此,在使用它们时会有(非常)小的性能开销。通常,如果向外部公开字段,则始终使用Property,如果需要验证值,则通常在内部使用Property。
其他回答
字段是类中的变量。字段是可以通过使用访问修饰符封装的数据。
财产与字段类似,它们定义与对象关联的状态和数据。
与字段不同,属性有一种特殊的语法来控制用户如何读取数据和写入数据,这些被称为get和set运算符。集合逻辑通常可用于进行验证。
财产的主要优点是允许您更改对象上数据的访问方式,而不会破坏其公共接口。例如,如果您需要添加额外的验证,或者将存储的字段更改为计算字段,那么如果您最初将字段作为属性公开,则可以很容易地执行此操作。如果您只是直接公开了一个字段,那么您必须更改类的公共接口以添加新功能。这种改变会破坏现有的客户机,要求他们在使用新版本的代码之前重新编译。
如果您编写了一个为广泛使用而设计的类库(如数百万人使用的.NET Framework),这可能是一个问题。然而,如果您正在编写一个在小代码库内部使用的类(例如<=50K行),这真的不是什么大问题,因为没有人会受到您的更改的负面影响。在这种情况下,这真的只是个人喜好。
使用财产,您可以在属性值更改时(也称为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(){
NameChanging?.Invoke(this,EventArgs.Empty);
}
private void OnNameChanged(){
NameChanged?.Invoke(this,EventArgs.Empty);
}
}
虽然字段和财产看起来很相似,但它们是两个完全不同的语言元素。
字段是在类级别上存储数据的唯一机制。字段在概念上是类范围内的变量。如果要将一些数据存储到类(对象)的实例中,则需要使用字段。别无选择。虽然财产无法存储任何数据,但看起来它们可以这样做。请参阅下文。另一方面,财产从不存储数据。它们只是一对方法(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;
}
}
}
因此,正如您所看到的,编译器仍然使用字段来存储值,因为字段是将值存储到对象中的唯一方法。
正如你所见,虽然财产和字段有相似的用法语法,但它们是非常不同的概念。即使您使用自动财产或事件,隐藏字段也由编译器生成,真实数据存储在这里。
如果需要使外部世界(类的用户)可以访问字段值,请不要使用公共或受保护的字段。字段始终应标记为私有。财产允许您进行值检查、格式化、转换等,通常会使代码更安全、更可读、更可扩展,以便将来进行修改。
差异-用途(何时和为什么)
字段是直接在类或结构中声明的变量。类或结构可以具有实例字段或静态字段,也可以同时具有两者。通常,应仅对具有专用或受保护的可访问性的变量使用字段。类向客户端代码公开的数据应该通过方法、财产和索引器提供。通过使用这些构造间接访问内部字段,可以防止输入值无效。
属性是提供读取、写入或计算私有字段值的灵活机制的成员。财产可以像公共数据成员一样使用,但它们实际上是称为访问器的特殊方法。这使得数据可以很容易地访问,并且仍然有助于提高方法的安全性和灵活性。财产使类能够公开获取和设置值的公共方式,同时隐藏实现或验证代码。get属性访问器用于返回属性值,set访问器用于分配新值。
推荐文章
- net HttpClient。如何POST字符串值?
- 我如何使一个方法的返回类型泛型?
- 何时处理CancellationTokenSource?
- 如何获取正在执行的程序集版本?
- AutoMapper vs valueinjector
- 为什么控制台不。Writeline,控制台。在Visual Studio Express中编写工作?
- 什么是.NET程序集?
- 字符串不能识别为有效的日期时间“格式dd/MM/yyyy”
- 函数应该返回空对象还是空对象?
- 如何转换日期时间?将日期时间
- 如何在c#中连接列表?
- 在c#中引用类型变量的“ref”的用途是什么?
- 防止在ASP中缓存。NET MVC中使用属性的特定操作
- 转换为值类型'Int32'失败,因为物化值为空
- c#中有任何连接字符串解析器吗?