前言
Microsoft .net平台组成
底层操作系统:
windows
:
--Microsoft
.NET Passport XML Web
服务支持
--即时消息通知应用程序
--.NET 企业服务器:
l
Microsoft Application Center 2000
l
Microsoft BizTalk Server 2000
l
Microsoft Commerce Server 2000
l
Microsoft Exchange 2000
l
Microsoft Host Integration Server 200
l
Microsoft Internet Security and Acceleration(ISA) Server 2000
l
Microsoft Mobile Information Server 2002
l
Microsoft SQL Server 2000
--Microsoft XML Web 服务:.NET My Services
http://www.Microsoft.com/MyServices/
开发平台:.NET FrameWork
有两个部分:通用语言运行时(Common Language Runtime:CLR)和.NET 框架类库(Framework Class Library:FCL)
特征如下:
l
一致的编程模型。完全面向对象机制和丰富的FCL使编程模型一致
l
简化的编程方式。简化Win32和COM环境下所需的复杂基础构造(注册表、GUID、IUnknown、AddRef、Release、HRESULT等等)
l
可靠的版本机制。彻底消除“DLL hell”的大门
l
轻便的部署管理。安装程序不再使用注册表,而是直接进行拷贝
l
广泛的平台支持。对ECMA的CLR和FCL兼容的机器都可以运行
l
无缝的语言集成。遵循通用语言规范(Common Language Specification:CLS)书写的类型可以在不同语言之间互用
l
简便的代码复用。
l
自动化的内存管理(垃圾回收)。对内存以及诸如文件、屏幕空间、网络连接、数据库等资源的管理
l
坚实的类型安全
l
丰富的调试支持。CLR完全支持跨语言调试
l
统一的错误报告。强大的异常处理机制;CLR中的异常具有跨模块和跨语言特性
l
全新的安全策略。CLR中的代码访问安全(CAS)为我们提供了以代码为中心的安全控制方式
l
强大的互操作能力。对访问现有COM组件以、传统DLL以及WIN32函数提供支持
集成开发环境:Visual Studio .NET
对所有的开发语言,有统一的IDE。
第一章
??
Microsoft .NET
框架开发平台体系结构
一、
????????????
将源代码编译为托管模块:
?
1、
?
CLR的存在是得我们可以选择适合表达逻辑的语言,只要存在相应得编译器将代码编译成为面向CLR的代码即可,结果成为托管模块。
2、
?
托管模块(managed module):是一个需要CLR才能执行的标准windows可移植可执行文件(portable executable:PE),组成:
1)
???????
PE表头:指出文件类型,文件的时间标记
2)
???????
CLR表头:CLR版本,托管模块入口、元数据、资源、强命名、标记等信息
3)
???????
元数据:源代码中定义、引用的类型和成员
4)
???????
中间语言(IL)代码
关于元数据:
l
????????
总与IL代码同步
l
????????
省去源代码编译时对头文件和库文件的需求
l
????????
Visual Studio .NET利用之进行智能感知,辅助编码
l
????????
用于CLR的代码验证
l
????????
序列化及反序列化对象
l
????????
垃圾收集器可以追踪对象的生存期
二、
????????????
将托管模块组合为程序集
关于程序集的理解:
暂歇
三、
加载通用语言运行时
1、
?
通过在%window%\system32目录下查找MSCorEE.dll文件来判断一个机器中是否安装了.NET框架;而框架的版本可从注册表下的子键:
HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ .NETFramework \ policy中查看
2、
当生成一个
EXE
程序集时,编译器/链接器会产生一些特殊的信息,并将它们嵌入到结果程序集的
PE
文件表头及其各个组成文件的
.text
部分。当
EXE
文件被调用时,这些特殊的信息将导致
CLR
被加载并初始化。
CLR
随后会定位至应胜程序的入口点方法,从面以此来启动应用程序。
?
类似地,如果是一个非托管应用程序通过调用
LoadLibrary
来加载一个托管程序集,那么该托管程序集
DLL
的入口点函数也会知道去加载
CLR
来处理包含在其中的代码
3、
?
其他诸如
EXE
程序集或
DLL
程序集如何被执行的过程,可在考察普通
Windows PE
文件的执行过程后进行比较以明确执行过程
四、
执行程序集代码
1、
?
IL代码:一种面向对象的机器语言。可以理解对象类型,包含高级指令:创建和初始化对象、调用对象上的虚方法、直接操作数组元素、抛出和捕获异常;
IL汇编语言能获取CLR的所有功能,其他高级语言智能获取其中的一个子集;
IL代码独立于CPU平台,执行时要先被即时编译器(Just In Time Compiler)编译;
微软提供的IL汇编器:ILAsm.exe 反汇编器:ILDasm.exe。
2、
?
IL代码执行过程:
以下面代码段为例:
??????? static void Main()
??????? {
?????????????? Console.WriteLine(“Hello”);
?????????????? Console.WriteLine(“GoolBye”);
}
?0
)代码被执行之前:
???? CLR
首先检测Main中代码所引用到的所有类型,并分配一个数据结构记录该类型,数据结构为类型的每个方法分配一个对应条目,记录该方法实现代码的地址;初始化该结构时,各方法被设置为CLR内部的一个函数(未定义的函数)
1)
???????
第一次被执行时
当上述结构中某函数被调用时,JITComplier由该函数的类型及地址信息在程序集的元数据中找到其IL代码的位置,把这些IL代码验证并编译成本地CPU指令这些CPU指令被保存在一个动态分配的内存中,而a)步骤中数据结构中被调用方法的地址则被替换为包含本地CPU指令的地址。
2)
???????
第二次被执行时
若被调用的函数已经被验证并编译过,则直接从内存中调用之
若没有被调用则重复第一次被执行时的情况
3、
?
性能
托管代码可能比非托管代码效率更好的理由:
l
????????
JIT编译器可检测到新型CPU并产生为这些CPU提供的特殊指令,以优化执行效率
l
????????
JIT编译器可检测到总是返回错误的布尔测试,对于其所办函的代码段不产生CPU指令,以使得代码量更小,执行效率更高。
l
????????
CLR在运行时有针对性地重新编译某些IL代码,重新组织以提高分支预测的成功率。
4、
?
NGen.exe提供折中方案,为IL代码提供预编译版本,以避免运行时编译。
5、
?
IL代码验证:
--被验证为“不安全”的代码将抛出System.Security.VerificationException异常
--代码验证使得可以在一个windows进程中可以运行多个 托管应用程序
更多的讨论在
20
章
五、
.NET框架类库(FCL)
CLR和FCL允许开发人员创建以下几种应用程序:
l
????????
XML Web服务
l
????????
Web窗体
l
????????
Windows窗体
l
????????
Windows控制台应用程序
l
????????
Window服务
l
????????
组件库
制作特定的应用程序,关键是对相应得FCL的熟悉,那么首先对.NET Framework熟悉了吧。
六、
通用类型系统(Common Type System)
描述了CLR中类型的规范
类型的成员组成:
l
????????
字段:表明对象的状态
l
????????
方法:用来改变状态
l
????????
属性:提供对状态访问时输入参数验证、状态有效性检验及必要的求值运算等功能
l
????????
事件:对象间的通知机制
类型可见性和访问类型成员的一些规则:
l
????????
Private:只能被同一类型中的代码访问
l
????????
Family:可以被派生类中的代码调用,而不管是否位于同一个程序集中
l
????????
Family与Assembly:只能被位于同一个程序集中的派生类代码调用
l
????????
Assembly:只能被同一个程序集中的代码调用
l
????????
Family或Assembly:能被任何程序集中的派生类代码调用,也可以被同一程序集中的任何类型调用
l
????????
Public:可被任何程序集中的任何代码调用
七、
通用语言规范(Common Language Specification)
为使一种语言创建的类型能被其他语言无缝访问,微软定义了通用语言规范(CLS)。CLR/CTS支持的特性比CLS定义的丰富得多,实际上不同的语言实现了CLR/CTS特性的一个子集,而CLS则是所有语言特性的交集(一个最小特性集合)。
因此只有遵循CLS的类型才能被其他语言访问、应用,C#中使用:
[assembly:CLSCompliant(true)]
迫使编译器确保在公共导出类型中,剔除不符合CLS的部分。
八、
与非托管代码互操作
为避免重新实现所有现有代码,CLR被设计成包含托管部分和非托管部分,CLR支持三种互操作情形:
l
????????
托管代码调用DLL中的非托管函数
l
????????
托管代码使用现存的COM组件
l
????????
非托管代码使用托管类型
第七章
类型成员及其访问限定
一、类型成员的可能组成:
ü
????????
常数,总是静态不可变的
ü
????????
字段,分为静态和实例字段两种
ü
????????
实例构造器,初始化实例对象
ü
????????
类型构造器,初始化类型的静态字段
ü
????????
方法,分为静态和实例方法两种
ü
????????
重载操作符,并非CLS的一部分,因为并非所有语言均支持之
ü
????????
转换操作符,不是CLS的一部分,部分语言不支持
ü
????????
属性,分静态和实例属性
ü
????????
事件,分为静态和实例事件两种
ü
????????
类型
二、访问限定修饰符:[
仅列出c#
中的]
C#
术语
|
描述
|
private
|
仅可被所定义的类型(或其嵌套类型)访问
|
protected
|
仅可以被所定义的类型(或嵌套类型)或继承的类型访问
|
Internal
|
仅可以被锁定义的程序集访问
|
protected internal
|
仅可以被所定义的类型(或嵌套类型)、派生类型以及同一程序集访问
|
public
|
可被任何程序集的任何类型访问
|
注:1、默认的访问方式为Internal
?????? 2
、只能选择上述的一种修饰符,而不可同时指定两个
三、类型预定义特性:
C#
术语
|
描述
|
abstract
|
不可被实例化,可用作基类型
|
sealed
|
不能用作基类型
|
注:不可同时使用上述限定符,可通过为sealed修饰的类型提供private构造函数来达到“不可被实例化并不可被继承”的目的
四、字段预定义特性:
C#
术语
|
描述
|
static
|
字段为 类型字段
|
readonly
|
仅可在构造器中被赋值
|
注:关于常数和静态只读字段的区别,将在第八章详述
五、方法预定义特性
C#
术语
|
描述
|
static
|
类型方法,不能访问实例字段或方法,只能访问类型方法或类型字段
|
默认(CLR中称为Instance)
|
实例方法,可以访问实例方法或字段,也可以访问类型方法或字段
|
virtual
|
多态的实现,总调用继承链最末端的实现
|
new
|
仅用于虚方法,隐藏基类型的方法实现
|
override
|
仅用于虚方法,显示声明重写基类型方法
|
abstract
|
仅用于虚方法,派生类必须提供和该抽象方法匹配的实现,含有抽象方法的类型为抽象类型
|
sealed
|
仅用于虚方法,派生类不能重写该方法
|
注:sealed和abstract不能同时使用
第八章
常数与字段
一、常数
1、可被定义为常数的类型有:
2
基元类型:Boolean, Char, Byte, SByte, Decimal, Int16, Int32, UInt16, UInt32, Int64, UInt64, Single, Double
2
字符串:String
2
枚举类型
2、常数在编译后直接嵌入IL代码中,因此一个模块中的常数不能在运行时被另一模块获取,前者对常数进行的修改无法被另一个模块在运行时感知,因此要想在运行时获取“不变数值”应该使用只读字段
二、字段
1、字段(包括静态、实例或只读字段)均在运行时分配内存
2、只读字段只能在构造器中被符值(也可在声明时被直接赋值,其他地方均不允许,实际上同于在构造器中赋值),静态只读字段在类型构造器中赋值,实例只读字段在实例构造器中被符值。
3、静态只读字段只能使用类型名访问,而不能使用实例引用访问
三、静态只读字段和常数的区别:
1、常数直接编译时刻嵌入IL代码,在运行时不可重新读取,静态只读字段则可以在运行时重新读取
2、常数只能在声明处赋值,而静态只读字段则可在构造器中赋值
第九章
方法
一、
????????????
实例构造器
1、
?
前面提到用new操作符创建对象时的三部曲:
l
????????
为对象分配内存
l
????????
初始化对象的附加成员(方法表指针和SyncBlockIndex)
l
????????
调用实例构造器初始化实例状态
在分配内存时,系统将所有内存位置均置为0值,这就是为什么字段初始化而未赋值时均为0或null值。
不
调用
实例构造器的情况:
l
????????
调用Object.MemberwiseClone()方法创建实例(分配内存;初始化附加成员;将源对象字节拷贝到新创建的对象)
l
????????
反序列化对象时
2、
?
为避免为实例字段产生过多的构造器代码,应避免在声明字段时为字段符初值,而是在无参构造器中为它们符初值,在其他重载的构造器中调用无参构造器。
3、
?
值类型实例构造器
l
????????
C#编译器不会自动调用其构造器,必须显式调用构造器才能起作用
l
????????
C#编译器不允许为值类型定义无参实例构造器(下面会介绍可以定义无参类型构造器)
l
????????
不能为结构中的字段在声明的同时赋初值,可通过定义带参构造器的方式进行
l
????????
必须在结构的构造器中为所有字段赋初值
二、
????????????
类型构造器
1、
?
类型构造器的一些限制:
l
????????
不能带任何参数
l
????????
类型构造器总为私有的,不能用其他访问修饰符
2、类型构造器被调用的时机:
l
????????
第一个实例被创建,或者类型的第一个字段或成员第一次被访问之前
l
????????
非继承静态字段被第一次访问之前
类型构造器在类型的生命周期中只被调用一次;
3、一些限制:
l
????????
若类型构造器中抛出异常,则该类型变成不可访问,访问其中的任何字段或方法均会抛出System.TypeInitializationException异常
l
????????
类型构造器只能访问类型的静态字段
l
????????
类型构造器不应该调用基类型的类型构造器,因静态字段并非继承而是编译时静态绑定
三、
????????????
操作符重载
1、
?
操作符重载
C#中对操作符重载的一些限制:
l
????????
必须声明为public static
l
????????
必须有一个参数为操作符所属类型
l
????????
不能改变操作符原始定义的引数个数
l
????????
若定义了true操作符也必须同时定义false操作符,二者都必须返回bool值
l
????????
++、--操作符必须返回其所隶属之类型的一个实例
l
????????
可被重载的一元操作符:+、-、!、~、++、--、true、false
l
????????
可被重载的二元操作符:+、-、*、/、%、!、^(异或)、<、>、<<、>>、==、!=、<=、>=
l
????????
不允许被重载的操作符:&&、||、=、?:、+=、-=、/=、%=、|=、^=、<<=、>>=,实际上其中一些“复式操作符”在二元操作符被重载后自动生成,而不能显式定义
l
????????
必须成对重载的操作符:(== ,!=)、(<,>)、(<=,>=)
l
????????
++、--操作符重载时不能区分其为前置或后置的
2、
?
操作符重载与语言互操作性
编译器会为重载的操作符生成一个特殊名称的方法,如+(加)操作符生成op_Addition()方法,并为该方法的定义条目上加上specialname标记。当某种语言不能进行操作符重载时,可以直接定义具有该特殊名称的方法,以在其他语言中调用;或直接调用具有该特殊名称的方法以适应某种语言不能解析操作符的限制。如:vb中不能重载操作符,可显式定义op_Addition()方法以在C#中调用;C#中定义的+操作符不能被VB识别,可显式调用op_Addition()方法获得同样的功能。
四、
????????????
转换操作符
转换操作符的一些限制:
l
????????
必须为public static
l
????????
必须指定关键字implicit或explicit,原则为:从本类型转换为其他类型使用implicit,将其他类型转换为本类型用explicit,不能都使用implicit
五、
????????????
方法参数
1、
?
引用参数
l
????????
缺省情况下为值传递
l
????????
标志为out的参数,在调用方法前
不必初始化,但返回之前必须赋值,没有被初始化的参数是不能被使用
l
????????
标志为ref的参数,在调用方法前
必须初始化,否则触发编译错误
l
????????
可以使用ref或out来进行方法的重载,但不能通过区分ref和out来重载方法
l
????????
按引用方式传递的变量(实参)必须和方法声明的参数(形参)类型完全相同,否则触发编译错误。
2、
?
可变数目参数
使用params关键字及对象数组的方式指定可变参数序列。一些限制:
l
????????
只有方法的最后一个参数才能使用可变数目参数
六、
????????????
虚方法
1、
?
虚方法的调用机理
CLR使用以下两个IL指令调用方法:
u
??????
call
?
根据类型(即引用的静态类型、声明类型)来调用一个方法
u
??????
callvirt
????
根据对象(即引用的动态类型、实际类型)来调用一个方法
对于虚方法使用call来调用的情况有:
l
????????
base.虚方法(),
l
????????
密封类型引用虚方法,因为没有必要检验密封类型的实际类型
l
????????
值类型,避免被装箱
使用callvirt调用非虚方法的情况:
l
????????
应用变量为null时,使用callvirt才会抛出System.NullReferenceException异常,而call不会抛出
无论call或callvirt调用方法,均会有一个隐含的this指针作为方法的第一个参数,它指向正在操作的对象
2、
?
虚方法的版本控制:
用下面的例子说明:
using System;
class BaseClass
{
?????? public void NonVirtualFunc()
?????? {
????????????? Console.WriteLine("Non virtual func in base class");
?????? }
??????
?????? public virtual void VirtualFunc()
?????? {
????????????? Console.WriteLine("Virtual func in base class");??
?????? }
}
class DevicedClass : BaseClass
{
?????? //
若不使用new 关键字则编译器会有warning:
?????? //
“DevicedClass.NonVirtualFunc()”上要求关键字
?????? //new
,因为它隐藏了继承成员“BaseClass.NonVirtualFunc()”
?????? public new void NonVirtualFunc()
?????? {
????????????? Console.WriteLine("Non virtual func in deviced class");?????
?????? }
?????? //
若不添加关键字override或new,则编译器会有warning:
?????? //
“DevicedClass.VirtualFunc()”将隐藏继承的成员“BaseClass.VirtualFunc()
?????? //
”。若要使当前成员重写该实现,请添加关键字 override。否则,添加关键字
?????? //new
。
?????? public override void VirtualFunc()
?????? {
????????????? Console.WriteLine("Virtual func in deviced class");?????
?????? }
}
class TestClass
{
?????? public static void Main()
?????? {
????????????? //
派生类实例调用 非虚 及 虚函数
????????????? DevicedClass dc = new DevicedClass();
????????????? dc.NonVirtualFunc();
????????????? dc.VirtualFunc();
????????????? //
基类实例调用 非虚 及 虚函数
?????????????
????????????? BaseClass bc = new BaseClass();
????????????? bc.NonVirtualFunc();
????????????? bc.VirtualFunc();
?????????????
????????????? //
指向派生类实例的基类引用 调用 非虚 及 虚函数
????????????? BaseClass bc1 = dc;
????????????? bc1.NonVirtualFunc();
????????????? bc1.VirtualFunc();
?????? }
}
/*
在虚函数上使用关键字override的运行结果:
Non virtual func in deviced class
Virtual func in deviced class
Non virtual func in base class
Virtual func in base class
Non virtual func in base class
Virtual func in deviced class
*/
/*
在虚函数上使用关键字new的运行结果
Non virtual func in deviced class
Virtual func in deviced class
Non virtual func in base class
Virtual func in base class
Non virtual func in base class
Virtual func in base class
*/
由上可见:new 和 override在派生类中协调版本的控制,在第七章中已经看到oeverride只能用于virtual方法,new则可用于非虚或虚方法,以实现隐藏基类中的同名方法。在虚函数上使用override,重写了基类的方法,并无隐藏,这也就实现了多态。我们可设想这样的结论:
new
使用
call
指令调用静态类型的方法,而
override
使用
callvirt
指令调用动态类型的方法。
希望这个例子对您的理解有所帮助。
第十章
属性
摘要:
本章讨论C#中的 属性 及 索引器
一、属性
分为
静态属性、
实例
属性和
虚
属性
l
避免
直接访问类型字段或使用烦琐的
访问器
方法进行访问
l
很好的实现了类型的
数据封装,如:改变字段而维持属性的意义对用户是透明的
l
代码量小,运算量小的操作才使用属性,否则使用方法调用更合适
二、索引器
l
可有多个重载的索引器,只要参数列表不同即可
l
可通过应用System.Runtime.CompilerServices.IndexerNameAttribute特性改变编译器为索引器生成的方法名(缺省使用get_Item(…),set_Item(...))
l
不能通过上述改变方法名的办法来定义多个参数列相同而仅名称不同的索引器
l
没有所谓“静态索引器”
注:在属性或索引器中添加对参数或value值得判定有助于保证程序的完整性
一个简单的示例:
using System;
class IndexerTest
{
private static string[] strArr = new string[5];
IndexerTest()
{
for(int i = 0; i < 5; i ++)
{
strArr[i] = i.ToString();
}
}
public string this[Int32 nIndex]
{
get{
return strArr[nIndex];
}
set{
strArr[nIndex] = value;
}
}
//
提供不同的参数列进行重载索引器
public string this[byte bIndex]
{
get{
return strArr[bIndex];
}
set{
strArr[bIndex] = (string)value;
}
}
//
只读属性
public string[] StrArr
{
get{
return strArr;
}
}
public static void Main()
{
IndexerTest it = new IndexerTest();
it[1] = "Hello"; //
利用索引器进行写操作
foreach(string str in it.StrArr)
{
Console.WriteLine(str);
}
}
}
<div style