9159金沙游艺场

图片 3
将字符串转换成gb2312或者utf-8编码的参数(js版)
图片 12
有趣的CSS题目(6): 全兼容的多列均匀布局问题

[C#6] 5-自动属性增强

C#中字段、属性和构造函数赋值的问题

0. 目录

C#6
新增特性目录

提出问题

首先提出几个问题:

1、如何实现自己的注入框架?

2、字段和自动属性的区别是什么?

3、字段和自动属性声明时的直接赋值和构造函数赋值有什么区别?

4、为什么只读字段和只读自动属性(只有get没有set访问器)都可以在构造函数中进行赋值?

5、反射可以给只读字段或者只读属性进行赋值吗?

6、自动属性和普通属性的区别?

这些问题是我在试着写自己的注入实现时遇到的问题。这些问题应该在学习C#时的第一节课就应该学到了,我看网上还有人分享说他在面试时遇到面试官问为什么只读字段和只读自动属性可以在构造函数中进行赋值,他没有回答上来,然后他写文章探讨这个问题,却没有得出一个明显的答案,实在可惜。网上关于只读属性有些是写ReadOnly特性的,读到这些文章直接跳过吧,老版本的C#现在看也没什么帮助。

1. 老版本代码

 1 internal class Person
 2 {
 3     public string Name { get; private set; }
 4     public int Age { get; private set; }
 5 
 6     public Person(string name,int age)
 7     {
 8         Name = name;
 9         Age = age;
10     }
11 }

通常情况下,C#的属性可以很好的帮助我们完成工作,比如上面的代码。在为属性赋值的时候,我们可以在任意地方为其赋值。但是并没有一种像是字段一样的声明且立即初始化的语法来简化默认值的设定。C#6为我们带来了这种新的语法,像是为字段赋值一样为属性赋值。

我们也知道,C#的属性实际上是一个编译器自动生成的私有字段、get_xxx和set_xxx、一条元数据组成,比如上面的代码编译后:

图片 1

<Name>k__BackingField字段的IL

1 .field private string '<Name>k__BackingField'
2 .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
3 .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 )

表示一个私有字段,第2行分别表示这个自动是编译器自动生成的,第3行表示该字段不显示在Debugger窗口中。

 

get_Name方法的IL:

 1 .method public hidebysig specialname instance string 
 2         get_Name() cil managed
 3 {
 4   .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
 5   // Code size       7 (0x7)
 6   .maxstack  8
 7   IL_0000:  ldarg.0
 8   IL_0001:  ldfld      string csharp6.Person::'<Name>k__BackingField'
 9   IL_0006:  ret
10 } // end of method Person::get_Name

这也是一个自动生成的方法。

 

set_Name方法的IL:

 1 .method private hidebysig specialname instance void 
 2         set_Name(string 'value') cil managed
 3 {
 4   .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
 5   // Code size       8 (0x8)
 6   .maxstack  8
 7   IL_0000:  ldarg.0
 8   IL_0001:  ldarg.1
 9   IL_0002:  stfld      string csharp6.Person::'<Name>k__BackingField'
10   IL_0007:  ret
11 } // end of method Person::set_Name

同样是一个自动生成的方法。

 

Name属性的IL:

1 .property instance string Name()
2 {
3   .get instance string csharp6.Person::get_Name()
4   .set instance void csharp6.Person::set_Name(string)
5 } // end of property Person::Name

表示Name属性由一个get方法和set方法组成。

给出答案

2、属性比字段多了get/set访问器;字段是在内存中声明的一个内存空间,可以实实在在的存储值;属性像字段一样使用,却可以有自己的代码段,能赋值取值,是因为访问属性就是调用属性的get/set方法对字段进行取值赋值(或者不操作字段);在MSDN上,建议字段作为类的私有变量使用private/protected修饰,属性则往往作为共有属性使用public修饰;字段的读取和操作都是直接操作内存,属性是调用get/set访问器,所以字段比属性快。

3、准确来说,没有区别。区别仅仅是直接赋值先执行,构造函数赋值后执行。在生成的IL中间语言(C#代码先编译成IL代码,然后才编译成汇编语言)中,字段直接赋值和构造函数赋值是在同一个代码段中(构造函数中)的。

4、这个问题可以和上面的问题联合起来回答。构造函数作为实例化一个类的入口,是最先访问的。字段的直接赋值其实也是放在构造函数中执行的,所以才说直接赋值和构造函数赋值没有区别。“只读”的限制只是由C#编译器(CLR)维护的,我觉得全名应该叫做“除构造函数外只读”更加准确,这是C#语法的规则记住就行(这是当然,直接赋值其实是放在构造函数中进行赋值的,如果构造函数不能赋值那只读字段没有值和没有声明一样);

5、这个问题又可以和上面的问题联系起来一起回答。通过反射可以给自读字段赋值但是无法给只读属性进行赋值(不相信的可以试一下)。对只读字段的赋值是因为绕过了C#编译器(CLR)的只读显示,对只读属性赋值的话是还是调用set访问器对字段进行赋值,因为没有set访问器所以允许后会报错。那么问题来了,那为什么只读自动属性没有set访问器还可以在构造函数中赋值呢?其实只读自动属性在构造函数中进行赋值,实质上是对字段进行赋值,和属性的get/set访问器没有关系。

6、区别是什么?上面一直强调自动属性,是因为自动属性和普通属性不一样,比如只读普通属性(没有set访问器)无法在构造函数中赋值。在没有自动属性之前,普通属性使用步骤是首先声明一个字段如_id,然后声明一个属性Id,在get和set访问器中做一些操作,这些操作大多数是对字段_id的操作,但是有时候和字段没有关系。普通属性可以像字段一样通过“.”的方式调用,但又像方法一样具有代码段(普通属性从来不开辟内存空间)。

但是C#3.0之后引入了自动属性,声明方式如public
int id { get; set; },C#6.0之后又有了public string FirstName { get;
set; } = “Jane”。自动属性肯定开辟了内存空间然后才有了自动属性的直接赋值。其实在类中声明自动属性会在编译成IL中间语言中声明一个隐藏字段,然后生成隐藏字段的get/set方法,然后生成get/set访问器。这里可以解释为什么只读普通属性无法在构造函数中赋值(和直接赋值)而只读自动属性可以在构造函数中赋值(和直接赋值),因为不论直接赋值还是在构造函数中赋值,生成的IL代码中的构造函数中,操作的都是隐藏字段,并没有访问属性的set访问器。(注意这里只是说的类中的自动属性,接口中也是可以有自动属性的,但是接口的自动属性并不会生成隐藏字段只是定义get/set访问器)

2. 自动属性增强语法

 1 internal class Person
 2 {
 3     //声明读写属性、且初始化默认值
 4     public string Name { get; set; } = "blackheart";
 5 
 6     //声明只读属性、且初始化默认值
 7     public int Age { get; } = 1;
 8 
 9     //声明只读属性
10     public string Note { get; }
11 
12     public Person(string note)
13     {
14         //在构造器中为只读属性初始化默认值
15         Note = note;
16     }
17 
18     private void func1()
19     {
20         //error,只能在构造器中初始化
21         //Note = "123";
22         //Age = 1;
23         //可以修改,因为有set访问器
24         Name = "new name";
25     }
26 }

这种新语法会在没有set访问器的时候把隐藏的私有字段设置为只读字段(readonly
),只允许在声明的时候设置初始值或者在构造器里面赋值。看看IL:

图片 2

只有Name属性具有set_Name方法,而Age和Note属性则没有set访问器,且对应的私有字段被设置为”initonly”,表示这是一个只读字段。

构造器方法,Name{get;set;}=”blackheart”和Age{get;}=1的初始化操作部分被转移到实例构造函数”.ctor”方法中。

 1 .method public hidebysig specialname rtspecialname 
 2         instance void  .ctor(string note) cil managed
 3 {
 4   // Code size       34 (0x22)
 5   .maxstack  8
 6   IL_0000:  ldarg.0
 7   IL_0001:  ldstr      "blackheart"
 8   IL_0006:  stfld      string csharp6.Person::'<Name>k__BackingField'
 9   IL_000b:  ldarg.0
10   IL_000c:  ldc.i4.1
11   IL_000d:  stfld      int32 csharp6.Person::'<Age>k__BackingField'
12   IL_0012:  ldarg.0
13   IL_0013:  call       instance void [mscorlib]System.Object::.ctor()
14   IL_0018:  nop
15   IL_0019:  nop
16   IL_001a:  ldarg.0
17   IL_001b:  ldarg.1
18   IL_001c:  stfld      string csharp6.Person::'<Note>k__BackingField'
19   IL_0021:  ret
20 } // end of method Person::.ctor

和之前的语法生成的代码可以说是一致的,均是生成为一个字段、get_xxx和set_xxx方法和对应的属性元数据,本质依然是编译器的语法简化。

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图