您的位置:首页 > 编程语言 > ASP

常量,字段,构造方法 调试 ms 源代码 一个C#二维码图片识别的Demo 近期ASP.NET问题汇总及对应的解决办法 c# chart控件柱状图,改变柱子宽度 使用C#创建Windows服务 C#服务端判断客户端socket是否已断开的方法 线程 线程池 Task .NET 单元测试的利剑——模拟框架Moq

2018-05-27 23:10 1581 查看

常量,字段,构造方法

 

常量

1.什么是常量

​ 常量是值从不变化的符号,在编译之前值就必须确定。编译后,常量值会保存到程序集元数据中。所以,常量必须是编译器识别的基元类型的常量,如:Boolean,Char,Byte,SByte,...,...,...,UInt64,Single,Double,Decimal,String。另外,C#是可以定义非基元类型的常量的,前提是值必须为null。

public sealed class SomeType
{
public const SomeType Empty=null;
}

 

2.常量的特性

  • 常量成员将创建元数据,它是直接嵌入在代码内部,运行时不需要额外分配内存。

  • 常量被视为静态成员,而不是实例成员。

  • 不能获取常量的地址

  • 不能以引用的方式传递常量

  • 参考上面的特性,如果跨程序引用,尝试改变常量初始值,不仅dll需要重新编译,引用者也需要编译

字段

1.什么是字段

字段是一种数据成员,它可以是值类型的实例也可以是引用类型的引用。

CLR支持类型字段和实例字段,什么是类型字段?它其实就是我们熟悉的静态字段,实例字段就是非静态字段。

1.1类型字段(静态字段)的内存分配过程

类型对象(静态对象)是在类型加载到一个AppDomain时创建的,而所需内存也是在内型对象中分配的。

接着上面的问题,那么,什么时候将类型加载到AppDomain中内?当第一次对引用到该类型的方法进行JIT编译时,

1.2实例字段的内存分配过程

实例字段的内存,是在构造容纳字段的类型进行实例构造时分配的。

2.字段特性

字段存储在动态内存中,它不像常量,所以只能在程序运行时,才能够获取到它的值。字段可以是任何类型,不像常量有类型上的限制。

2.1字段修饰符

Staticstatic指定字段为类型的一部分,而不是对象的一部分
Instance 默认 指定字段与实例关联,而不是和类本身关联
InitOly readonly 只能在构造器方法中进行值的写入,否则只读
Volatile volatile 表示,编译器和CLR以及硬件,不会对这种字段标识的代码执行“线程不安全的措施”,只有CLR中的基元类型能使用这个修饰符。

2.2 readonly和read/write

通常,字段都是read/write,即可读可写的,这也意味着,字段的值会随着运行可能发生值得变化。而当你把字段标记为readonly,那么你就只能在构造函数中,对它进行赋值,编译器是不会允许你在构造器(构造函数)以为的任何方法写入值,或变更值。

当然,C#提供了一种内联初始化的语法糖来进行readonly值的初始化,这种语法也可以对常量和其他形式的字段进行赋值。

public readonly int =250;

当然,使用内联语法,而不是在构造器中构造,滥用的话可能会有一些性能问题(代码膨胀等)。

构造方法

实例构造器(引用类型)

什么是构造器?

构造器是将类型的实例初始化到良好状态的特殊方法。在“方法定义元数据表”中始终叫.ctor(constructor的简称)。

引用类型在内存中如何实例化?

首先为实例的数据字段分配内存空间,然后是为初始化对象的附加字段(没错,就是我们经常会提到的同步块索引和类型对象指针)分配内存,然后最后开辟一个空间来调用实例构造函数进行对象的初始化。

在调用构造器之前,为对象分配的内存总是先被归零,为了保证那些被构造器显示重写的字段都获得0或者null的值。

 

实例构造器的特性:

  • 实例构造器永远不能被继承,类必须执行自己的构造函数。如果没有,系统默认会构造一个无参的。

  • 所以,实例构造器不能用new ,override,sealed和abstract修饰

  • 如果类的修饰符为abstract,那么构造器可访问性默认为protected,否则默认为public。

  • 如果基类没有提供无参构造函数(意味着显示的实现了有参的构造函数),那么派生类必须显示调用一个基类的构造器(及为了保证参数一致),否则编译报错。

  • static(sealed和abstract)修饰的类,编译器不会为它生成默认的构造函数

  • 通常情况下,无论如何实例化派生类,基类的构造函数一定会被调用,所以object的构造函数一定会被先调用,但是实时上它什么也不会干。

  • 极少数情况下,对象实例不会调用构造函数。如,Object的MemberwiseClone方法,它是用来分配内存,初始化对象的附加字段的,然后将源对象的字节数据复制到新对象中。

  • Notice:不要在构造器中调用虚方法。因为,假如被实例化的类型重写了虚方法,就会执行派生类型中的实现,但这个时候,却是没有初始化的,所以,容易导致无法预测的行为。

内联语法(在字段一节提到过)方式实现初始化实例字段,其实也是转换成构造器方法中的代码来实现。

实例构造器(值类型)

CLR是允许值类型创建实例,但是c#编译器是不会默认为值类型构建构造函数的,并且值类型构造器必须显示调用才执行。如上面所说,即使你自己定义了一个构造函数,不管它是有参还是无参,编译器都不会去自动调用它,如果你想执行,必须自己显示进行调用。

然而,上面说那么多,在C#中,编译器根本不允许你定义值类型的无参构造函数,它会报:error CS0568:结构不能包含显示的无参构造函数。

同理,你不能对值类型的字段成员进行内联赋值,因为内联语句实际上是通过构造器进行赋值,如下面的代码:

internal struct SomeValType
{
private int m=5;
}

 

上面的代码,会报:结构中不能有实例字段初始值设定项。

所以,值类型的字段总是被初始化为0或null,因为没有真正意义上的构造函数为它初始化其他值,只有你手动去调用构造函数(所以这里我们不理解为初始化)。

当你提供一个有参构造函数时,你需要为所有的字段进行赋值,否则会报:error CS0171:在控制返回到调用方法之前,字段XXX必须完全赋值。

类型构造器(静态构造器)

什么是类型构造器?

实例构造器是为了让类的实例有一个良好的可验证的初始值。而类型构造器是为静态类型服务,顾名思义,类型构造器则是为了让类型有良好的初始状态。

类型构造器特征

  • 默认没有构造函数

  • (类型)静态构造器永远不能有参数

  • 必须标记为static,因为静态类型的成员必须为静态成员

  • 不能赋予任何访问修饰符,默认为隐式类型,C#默认为private

  • 类型构造器中的代码只能访问类型的静态字段(常规用途就是初始化这些字段)

类型构造器的调用过程

类型构造器调用过程大致如下:

JIT编译器在编译到一个静态方法时,会查看引用了哪些静态类型。如果这个静态类型定义了一个构造函数,JIT编译器会检查当前AppDomain,是否已经执行过了这个类型构造器。如果已经执行过,就不添加对它的调用。如果从未执行过,JIT编译器会在它的本机代码中添加对类型构造器的调用。

重要的是:为什么静态类型的特性是十分适合做单例呢?因为CLR常常是确保每一个AppDomain中,一个类型构造器都只执行一次,那么上述的机制不足以很好的支撑这个特性,因为,多个线程下如何保证呢?为了保证这一点,调用类型构造器时,每一个调用线程都会获取一个互斥线程同步锁,在这样的机制下,如果多个线程试图同时调用某个类型的静态构造器,只有一个线程可以获得锁,其他的线程会被阻塞。只有第一个线程会执行静态构造器的代码。当一个线程离开构造器后,正在等待的线程才会被唤醒,后面的线程会发现,类型构造器已经被执行过了,将直接从构造方法返回。这样就能确保不会被再次调用。并且以上是线程安全的。

所以,单例模式就是借助上面的特性,你想构建的单例对象,则也应该放到类型构造器中进行初始化。

注意:值类型中也可以定义类型(静态)构造器,但是是不推荐这么做的,因为有时候CLR有时不会调用值类型的静态类型构造器。



internal struct StructValType
{
//虽然值类型的构造函数必须有参数,但是这个是静态构造函数,所以它是一定没有参数的,也不用遵守,必须初始化所有成员的值
static StructValType()
{
Console.WriteLine("我会出现吗?");
}
public int x;
}
​
class BaseClass
{
public string ClassName { get; set; }
​
static BaseClass()
{
Console.WriteLine("I'm BaseClass static Constructor without param");
}
​
public BaseClass()
{
Console.WriteLine("I'm BaseClass Constructor without param");
}
}

 

上述代码,BaseClass中和StructValType中都有static构造函数,再对两个类进行实例时,你可以发现值类型的静态函数是没有被调用的。

注意:单个线程中,两个类型构造器包含互相引用的代码可能出问题,因为你无法把握两者的实现顺序,也就无法保证能正确的引用。因为是CLR负责类型构造器的调用,所以不能要求以特定的顺序调用类型构造器。

如果,类型构造器抛出未处理的异常,CLR会认为类型不可用。试图访问该类型的任何字段和方法都会抛出System.TypeInitializationException异常。

 

 

调试 ms 源代码

 

如果需要调试 WPF 源代码或框架源代码,那么需要使用 DotPeek

首先需要下载 dotPeek ,可以到官网下载 dotPeek: Free .NET Decompiler & Assembly Browser by JetBrains 还可以到 csdn 下载

首先打开 dotPeek 然后点击启动符号服务器,所有符号。

然后点击工具设置,可以看到这个页面

然后打开 VS 工具选项,在调试设置符号,刚才已经复制了,现在添加就好

然后还需要去掉微软的服务和本地缓存

然后写一个呆磨进行测试

现在就可以开始调试框架源代码了

只需要在一些函数使用断点,然后堆栈跳转,假如我在 MouseDown 写一个断点,在触发按下,点击堆栈,可以看到外部代码。右击外部代码显示,这样就可以看到 垃圾wr 做的,双击他,可以跳到一个页面,点击加载就可以。

这时候可以看到 dotPeek 在反编译,这个时间比较长,需要去做一些你喜欢做的事情,回来就可以发现 dotPeek 反编译好而且你看到 ms 源代码,这时候可以尝试源代码断点,但是不是所有地方都可以断点。

如果你发现无法进入代码,那么尝试安装 Resharper ,如果还是不行,那么需要问一下,是不是使用 UWP ,因为现在我尝试 UWP 还没有成功。

如果还是无法成功,不要来问我,我教了几个小伙伴,有几个是没法进入代码,使用方法都一样,我自己去他电脑弄了,结果我无法进入。

那么接下来就是调试 ms 源代码了,因为已经进入了 Release 的反编译代码,所以通过堆栈调用就进入了源代码,在需要的地方使用断点,当然,不是所有地方可以使用断点。但是进入之后还是可以和原来的调试自己代码一样,看到没有被优化掉的参数的值,可以修改这些值,可以进入其他地方代码设置断点,设置条件,已经使用单步调试跟着代码。

在 win10 下,调试的代码是没有注释的,但是可以对比 dotpeek 的代码来看,一般他里面的代码就是有注释的,反编译的代码和 dotPeek 看到代码有些地方是不同的,但是实际功能是一样的。但是微软源代码使用的框架可能和自己的不一样,看起来代码还是不相同。

最好是自己去下载微软源代码,然后把他放在一个仓库,这样可以看到不同的框架修改的代码。

因为 UWP 编译使用 .netNative ,很多底层都是使用 C++ 写的,所以无法对 UWP 进行反编译

我搭建了自己的博客 https://lindexi.gitee.io/ 欢迎大家访问,里面有很多新的博客。只有在我看到博客写成熟之后才会放在csdn或博客园,但是一旦发布了就不再更新。

如果在博客看到有任何不懂的,欢迎交流。


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐