属性
属性的作用就是保护字段,对字段的赋值和取值进行限定
属性的本质就是两个方法,一个叫get()对取值进行限定,一个叫set()对存值进行限定,属性只是对属性的再赋值。
如果只有get是只读属性,set是只写属性。在get里面用的值是字段的值,set里面用的是value的值
所以在类里面的public 方法要访问私有字段,尽量访问属性,虽然这样可能会降低访问的效率,但是一方面会更安全地访问字段,另一方面可能是必须经过属性里的某个方法的处理才能得到这个字段的值。
代码风格
下面的例子展示了使用属性保护字段的过程,并且使用了异常处理来处理异常,并把错误信息返回而不是显示出来,这样体现了界面与业务相分离,也就是这个Person类既可以用在字符界面下,也可以用在GUI界面下,就用复用性。
这里把构造函数设置成私有的是因为,构造函数没有返回值,所以总会创建一个对象,而在Name属性的set方法里面抛出一个异常在构造函数里并不方便处理,所以把这个处理工作放在了GetAPerson这个Person类的静态方法里,这是外界创建Person类的唯一接口,也可以通过这种方法实现单例程序。
另外这个程序还是用了c#的异常处理机制,实在是方便(相比c++而言啦),Message是Exception的一个属性。
c#源代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace 属性与字段{ class Program { static void Main(string[] args) { string result; Person p = Person.GetAPerson("", out result); if (p==null) { Console.WriteLine(result); } Console.ReadKey(); } } class Person { private string _name; public string Name { get { return _name; } set { if (value == "") { throw new Exception("名字不能为空"); } else { _name = value; } } } private Person(String name) { Name = name; } /// <summary> /// 外界获得一个Person对象的唯一借口; /// </summary> /// <param name="name">Person的名字</param> /// <param name="result">如果创建对象成功,result存储"成功",失败则result存储失败的原因</param> /// <returns>返回创建的对象,如果对象创建失败返回NULL</returns> public static Person GetAPerson(string name,out string result) { Person tmp; try { tmp = new Person(name); result = "成功"; return tmp; } catch(Exception e) { result =e.Message;//Message是一个属性; //Console.WriteLine("抓住了"); return null; } } }}
IL中间代码
我们我们通过reflector来分析一下这个代码的中间代码;
set方法
其中set方法如果转换为IL中间代码则变为
.method public hidebysig specialname instance void set_Name(string 'value') cil managed{ // token: 06000002 .maxstack 2 //表示程序栈有两个变量 .locals init ( //定义一个bool类型变量,压入call stack里面 [0] bool CS$4$0000) L_0000: nop // 00 //什么也不做 L_0001: ldarg.1 // 03 //把第一个形参拿进来,就是value L_0002: ldstr "" // 7201000070 L_0007: call bool [mscorlib]System.String::op_Equality(string, string) // 281200000A L_000c: ldc.i4.0 // 16 L_000d: ceq // FE01 L_000f: stloc.0 // 0A L_0010: ldloc.0 // 06 L_0011: brtrue.s L_001f // 2D0C L_0013: nop // 00 L_0014: ldstr "\u540d\u5b57\u4e0d\u80fd\u4e3a\u7a7a" // 7203000070 L_0019: newobj instance void [mscorlib]System.Exception::.ctor(string) // 731300000A L_001e: throw // 7A L_001f: nop // 00 L_0020: ldarg.0 // 02 L_0021: ldarg.1 // 03 L_0022: stfld string 属性与字段.Person::_name // 7D04000004 L_0027: nop // 00 L_0028: ret // 2A}
我们可以分析几句重要的,其它的我都做了注释
if (value == ""){ throw new Exception("名字不能为空");}else{ _name = value;} L_0001: ldarg.1 // 03 //把第一个形参拿进来,就是value L_0002: ldstr "" // 7201000070 L_0007: call bool [mscorlib]System.String::op_Equality(string, string) // 281200000A L_000c: ldc.i4.0 // 16 L_000d: ceq // FE01
ldarg.1把形参value拿过来,ldstr “”找一个字符串”“的引用拿过来,然后调用op_Equality方法比较两者一样不,ldc.i4.0表示表达式的值是这个0先压入到栈里,为了方便一会儿跳转判断用,ceq如果比较结果为真,则产生一个值为1的int变量,如果结果为假,产生一个值为0 的变量;把这个变量压入刚才的0上面。
L_000f: stloc.0 // 0A L_0010: ldloc.0 // 06 L_0011: brtrue.s L_001f // 2D0C L_0013: nop // 00 L_0014: ldstr "\u540d\u5b57\u4e0d\u80fd\u4e3a\u7a7a" // 7203000070 L_0019: newobj instance void [mscorlib]System.Exception::.ctor(string) // 731300000A L_001e: throw // 7A L_001f: nop // 00 L_0020: ldarg.0 // 02 L_0021: ldarg.1 // 03 L_0022: stfld string 属性与字段.Person::_name // 7D04000004
stloc.0 和ldloc.0是刚才比较的结果产生的那个int值从栈里弹出来,
brtrue.s L_001f 讲如果栈顶是1就运行到L_001f ,很明显刚才1弹出来之后,栈顶是0(上面ldc.i4.0的作用),则继续往下运行,先加载一个静态字符串”\u540d\u5b57\u4e0d\u80fd\u4e3a\u7a7a”,就是我们为Exception传的实参”名字不能为空”的unicode编码,可以在Unicode转换中文工具这个转换,然后调用Exception的构造函数,在然后调用throw跳转。
GetAPerson方法
下面是GetAPerson方法的部分IL中间代码
L_0002: ldarg.0 // 02 L_0003: newobj instance void 属性与字段.Person::.ctor(string) // 7303000006 L_0008: stloc.0 // 0A L_0009: ldarg.1 // 03 L_000a: ldstr "\u6210\u529f" // 7211000070 L_000f: stind.ref // 51 L_0010: ldloc.0 // 06 L_0011: stloc.2 // 0C L_0012: leave.s L_0022 // DE0E L_0014: stloc.1 // 0B L_0015: nop // 00 L_0016: ldarg.1 // 03 L_0017: ldloc.1 // 07 L_0018: callvirt instance string [mscorlib]System.Exception::get_Message() // 6F1500000A L_001d: stind.ref // 51 L_001e: ldnull // 14 L_001f: stloc.2 // 0C L_0020: leave.s L_0022 // DE00 L_0022: nop // 00 L_0023: ldloc.2 // 08 L_0024: ret // 2A.try L_0001 to L_0014 catch [mscorlib]System.Exception handler L_0014 to L_0022
最后一句.try L_0001 to L_0014 catch [mscorlib]System.Exception handler L_0014 to L_0022表明如果catch到异常就执行L_0014 to L_0022的代码
reflector技能
可以把exe反编译成c#,VB,IL中间代码等。
当你看一个方法看不到的时候,可以先转换为IL中间代码。