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

提出问题

首先提出几个问题:

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

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

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

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

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

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

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

给出答案

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访问器)

开始解释

通过C#生成的IL中间语言代码可以知道的更清楚

    public class User
{
public int id = ;
public int age { get; set; } = ;
public User()
{
id = ;
age = ;
}
}

可以看到,自动属性会生成一个名称为 '<age>k__BackingField' 的隐藏私有字段+私有字段的get/set方法+属性代码段;

可以看到IL代码生成了User的构造函数 .ctor,ctor是构造函数(Constructor)。

不论直接赋值还是构造函数赋值,都是在.ctor中执行的,并且操作的都是字段,自动属性的赋值操作的是隐藏字段。

  public interface IUser
{
int id { get; set; }
}

可以看到,接口中的自动属性并没有生成隐藏字段。

其他说明

1、上文中提到“反射可以给只读字段进行赋值但是无法给只读属性进行赋值”。无法给只读属性进行赋值是因为没有set访问器。但是我们已经知道了可以给字段赋值,并且只读属性会生成隐藏字段,那我们是不是可以通过给隐藏字段进行赋值间接达到给自动属性赋值的目的呢?答案是可以的!

定义User的只读自动属性

    public class User
{
public int age { get; } = ;
public User()
{
age = ;
}
}

控制台的反射赋值代码:

            var user = new User();
try { typeof(User).GetProperty("age").SetValue(user, ); }
catch{ Console.WriteLine("只读属性赋值失败");}
typeof(User).GetField("<age>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(user,);
Console.WriteLine(user.age);
Console.Read();

运行

2、因为隐藏字段是私有的,所以取到隐藏字段需要  BindingFlags.NonPublic

3、只读自动属性说明不想被访问到那为什么还要给它赋值呢?这个问题……做着玩,项目中我觉得也没有什么用到的机会……

C#中字段、属性、只读、构造函数赋值、反射赋值的相关的更多相关文章

  1. Django模型中字段属性choice的使用

    根据Django官方文档: from django.db import models class Student(models.Model): FRESHMAN = 'FR' SOPHOMORE = ...

  2. Linux下修改MySQL数据表中字段属性

    一.修改某个表的字段类型及指定为空或非空 alter table 表名称 change 字段名称 字段名称 字段类型 [是否允许非空]; alter table 表名称 modify 字段名称 字段类 ...

  3. WCF Service 转换为Web Service 中字段属性

    1.新建WCF服务,服务中包含对象 2.部署WCF服务,并将其转换为应用程序 3.通过添加服务引用,使用WCF服务 4.调用对应的对象时需要对应的值设置为True. 参考:https://cloud. ...

  4. ElasticSearch mapping中字段属性总结

  5. a标签中href属性引起的页面不跳转问题

    先简单描述问题,今天在做一个简单的提交页面的时候,碰到了跳转不了的问题.其中a标签的形式<a href="" onclick="submit()"> ...

  6. IL角度理解C#中字段,属性与方法的区别

    IL角度理解C#中字段,属性与方法的区别 1.字段,属性与方法的区别 字段的本质是变量,直接在类或者结构体中声明.类或者结构体中会有实例字段,静态字段等(静态字段可实现内存共享功能,比如数学上的pi就 ...

  7. 【C#】C#中的属性与字段

    目录结构: contents structure [+] 属性和字段的区别 无参属性 自动实现的属性 对象和集合初始化器 匿名类型 System.Tuple类型 有参属性 属性的可访问性 在这篇文章中 ...

  8. 反射获取类中的属性和set属性

    package framework.base; import java.beans.IntrospectionException; import java.beans.PropertyDescript ...

  9. 实例级别和类级别的static、构造函数、字段属性的简单介绍

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 实例级别 ...

随机推荐

  1. java selenium webdriver第四讲 应用小结

    部分api 1.访问网站 driver.get("http://www.baidu.com"); 或者 driver.navigate().to("http://www. ...

  2. MySQL GTID (二)

    MySQL GTID 系列之二 三.在线将GTID转化为传统模式 环境见上篇系列文章 关闭GTID,不用停止服务,不影响线上业务 3.1 关闭GTID复制,调整为传统复制 #SLVAE实例上停止复制 ...

  3. 【Git】一、Git简介

    一.什么是Git Git是一款免费的开源分布式版本控制系统,可以有效的,高速的处理从很小到非常大的文件. 二.Git VS SVN 1. Git 是分布式的,SVN 是集中式的 2. Git 存储的是 ...

  4. Multithread之为什么spinlock必须是volatile?

    [Multithread之为什么spinlock必须是volatile?] 1.编译器的优化 在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中:以后再取变量 ...

  5. 17.Letter Combinations of a Phone Number(Back-Track)

    Given a digit string, return all possible letter combinations that the number could represent. A map ...

  6. centos7 搭建svn服务

    linux(centos)下SVN服务器如何搭建?说到SVN服务器,想必大家都知道,可以是在LINUX下如何搭建SVN服务器呢?那么今天给大家分享一下linux(centos)搭建SVN服务器的思路! ...

  7. zabbix 2.0 安装

    2.0环境 采用Centos6.3_64位操作系统 Zabbix安装 Zabbix 2.0 for RHEL5: # rpm -ivh http://repo.zabbix.com/zabbix/2. ...

  8. Shadow Map 实现极其细节

    这里不介绍算法原理,只说说在实现过程中遇到的问题,以及背后的原因.开发环境:opengl 2.0  glsl 1.0. 第一个问题:产生深度纹理. 在opengl中每一次离屏渲染需要向opengl提供 ...

  9. Luogu 4449 于神之怒加强版

    挺套路的题,然而一开始还是想错了…… $\sum_{i = 1}^{n}\sum_{j = 1}^{m}gcd(i, j) ^ {k} = \sum_{T = 1}^{min(n, m)}\left ...

  10. Greeplum 系列(七) 权限管理

    Greeplum 系列(七) 权限管理 一.角色管理 Role 分为用户(User)和组(Group),用户有 login 权限,组用来管理用户,一般不会有 login 权限.初始化 gp 时创建了一 ...