• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C#属性、自动属性、字段之间的区别和理解

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

 

.ctor是构造方法的意思,注意委托其实也是有构造方法的(不过是编译器自动创建的是私有的)貌似它的参数一个是委托引用的方法所属的对象(或Type对象),一个是该方法的指针;

1.属性的概念其实和字段是有一定重合的;C#的属性它不是用来表述某个类具有什么样的行为,而是指某个类具有什么样的成员变量/对象,并且同时指定它可以被外界有什么样的操作;
所以按照这个概念其实属性就应该是完全的自动属性,而不应该再加一些其他操作,如果有其他操作应该是字段+方法来结合实现;

2.属性也可以是静态属性和私有属性,表示这个属性是属于某个类的,表示这个属性虽然对外提供get或set,但是只是对本类内这个外部范围,不对类外这个外部范围(外部概念可大可小);

3.然后对于抽象编程里,某个接口具有getXx();的方法签名它虽然是getXx()但是它是属于行为层面的,表示它的子类具有获得Xx的行为;这个Xx可以是字段也可以是属性(这里就是它们重合的地方);
它就是对于JavaBean里的具有getter和setter方法的字段,它的get,set不是代表这个类具有什么行为,而是代表这个属性具有什么行为;
而一个类实现接口是指这个类有什么行为,它显然和属性的get,set是不一样的,比如可以说某个类具有获取属性的行为,那么子类实现时返回的就是一个字段,因为通过属性的get其实也是返回一个字段;

4.属性它本质上是一个私有字段加两个方法组成【4.5自动属性出现后这个私有字段仍然存在只不过它应该叫unreachable string _name,然后public string Name {get;set;},这个字段只有该属性能访问反射也弄不出来,类似函数里的局部变量只有该函数能访问】,自动属性的这个私有字段.Net对它做了进一步的隐藏,使得只能通过编译器为属性Name自动创建的两个方法get_Name和set_Name来操作(不信可以去试一下,定义了Name属性且get set后这两个方法不能手动创建),这里之所以要弄成这种很不合标准的方法,其实就是为了和常用标准区别开来,防止用户本身还需要有个GetName方法,然后它可能用下划线get_name或Get_Name,而get_Name正好和这些都不一样是偏僻的方法,只有用户故意捣乱才会这样子写;这样的命名不是为了适应老版本里private string _name的命名风格,就是为了特殊化使得尽可能不和用户可能用到的命名方式一样而导致方法签名重名;这两个方法分别对应get和set,如果只有get那么只会生成get_Name方法;注意,如果不是自动属性,那么是编译器能够感知到的从而不会为该类(该Type)分配一个unreachable字段;

反射里的成员就类似一个和get_Name和set_Name方法的桥接对象,是用来提供给反射操作属性时和平时编码的一致体验,否则我们要在反射里实现this.Name = "mm";就必须获得set_Name(..)方法来操作,
这就和平时写代码的感觉是不一致的(也类似Java里将泛型setXx<T>()方法桥接到setXx()方法里),而有了Name这个反射属性作为桥接方法(桥接对象?)【桥接其实就类似转发】,我们就可以直接操作Name来自动桥接到set_Name等方法里;

5.对于为属性Name自动生成的两个方法set_Name和get_Name只是反射的时候能看到,但是调试的时候是没法进入的,也没法watch到,因此没法通过调试来看get_Name方法到底是怎么操作那个unreachable访问权限字段的;而虽然反射可以看到这两个方法,但是如果要改属性值还是通过属性去改比较好(这个对这个属性执行写操作会链接到set_Name方法上),这样其实也方便框架获取可序列化的成员;

6.总结(类的组件用Property,类的或类属性的描述参数用Field):字段和属性都是用来描述对象的组成,但是对于那种标准JavaBean(Model层,最多只是实现了ISerializable接口)的空间结构组成要用属性;
而对于那种工具类、服务类的Bean的空间结构组成用字段,除非该服务实现类里有某个具有JavaBean特质的空间结构,这个时候用属性;

7.这里再来进一步区分字段和属性的概念,比如人这个实例类,人有口、鼻子、眼睛、腿、手;然后人具有各种动作,比如跑步,然后跑步它有快慢,加上 people1这个对象它的跑步速度是10,
然后用这个例子来讲解属性和字段的区分点:
口、腿、手之列的是人身上的组件,因此是Property,而runSpeed是用来描述这个人的跑步速度,肯定也是存储在这个人对象里的(每个人跑步速度不一样),但是它不是一种组件,所以它用Field来表示;
还有其他的一些数据也是用字段,如腿多长,这个就是用字段,并且这个可以做成抽象,表示某些类具有可以设置腿多长的行为;
设置跑多快的行为;这种听起来还是挺符合人类思维的;而如果我们说某个动物类具有可以设置眼睛的行为,这就感觉怪怪的,因为眼睛它是实体上的一个组件而非参数;

再举个例子,杯子,杯子它有杯身和背盖(有的可能还有该类杯子特定的勺子),这些都是组件,用Property,但是它还有一些描述性的数据(总算想到一个比较贴切的词),如这个杯子能装多少水,这个就是描述信息;

如果是非自动属性,那么调用Name的反射设置值的时候然后间接调用set_Name这里会传两个参数,一个是this,一个是外部参数,然后set_Name将外部参数赋值到this所处的指针加上要赋值对象的在该对象的内存index上即可,而自动属性其实也是这样子赋值的,只不过自动属性的赋值的时候set_Name只知道要对this + index来赋值,而对非自动属性它还能知道index对应this 字段的名字是什么(一个只知道相对this对象起始位置的偏移量,一个不但知道偏移量还知道偏移到那个位置是this里哪个字段的起点);

或者说一个是没有命名的对象内部的某块空间,一个是有命名的对象的某块空间,显然没有命名的方式是更加精简内存的,否则肯定还需要一些地方用来描述this里每个位置的字段名是什么(类似磁盘里的卷头,用来存储有多少卷,然后每一卷的起始位置是哪里,每一卷的结束位置是哪里,每一卷的命名是什么,这里的磁盘就是对象,每一卷起始就是字段,unreachable的字段是没有卷名的,甚至在卷头里也没有偏移量,这些相对磁盘起始位置的某一卷的起始位置和结束位置存储在了操作方法内部的局部变量里,它在get_Name和set_Name里是两个常量(因为卷头里没有该块空间的位置和描述信息自然就无法反射),所以传入this对象后获得this的其实位置,然后分别加上this的起始位置就得到了那块匿名空间的起始位置和结束位置,然后进行操作;

A再举个例子,一栋大楼(对象)里有5层,第一层作为元数据存储区(卷头)不参与存储对象字段,然后我知道要找的字段名如name(对应某一层作为一块空间)【字段名存储在set_Name方法里的局部变量里】,只需要告诉我this指针(大楼起始地址),然后我就跑到一楼查看name的元数据信息包括起止地址类型等,从而知道我要找的name字段在3楼,所以我再跑三楼地板到天花板这部分获取数据或存储数据;这种方式就是非自动属性的实现方式,显然第一楼里需要花一些空间来存储name的一些描述信息;
B而现在我不知道我要找的是什么(但是类型是知道的,毕竟get_Name方法的返回值类型就是我需要的类型,我去找其实就是set_Name,而要找的数据信息就是set_Name里面的局部变量),我只是知道我要找的空间是第三楼的地板到天花板这部分(这个就是偏移量/起止地址,相对大楼一层,然后这个偏移量也是记录在set_Name和get_Name里;用完就释放,当然有份默认的用于初始化每次调用),所以我跑到大楼后(this指针告诉我大楼在哪)直接跑到三楼对三楼进行操作;
所以自动属性也算是一种优化;

注意,上面图里的代码的Price是对显式字段price的引用,而且反射是可以获得的,因此编译器这时候就不会创建一个没有元数据的空间来用于set_Price get_Price的操作了,而是set_Price里存储的偏移数据也是指向price这个有元数据的字段上;然后之所以还是需要有这个显式字段的反射信息 是因为这样的显式定义使得price不仅仅可以被set_Price操作,还能被类里的其他方法操作;


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#操作Excel文件发布时间:2022-07-14
下一篇:
C#中使用反射获取结构体实例发布时间:2022-07-14
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap