学习交流,欢迎转载。转载请注明文章来源:http://www.cnblogs.com/lgjspace/archive/2011/10/12/2218254.html

 

定义:
序列化:将对象转换为二进制数据就是序列化;
反序列化:将二进制数据还原为对象就是反序列化。
序列化/反序列化就是为了保持对象的持久化保存。

 

细节:
文件的后缀名不是给计算机看的,而是给人看的。

 

细节:
若需要让一个对象可以被序列化,则要在该对象的类的声明前面加上 “[Serializable]” 标识,而且还必须要求该类的所有字段和属性的类型都是可以被序列化的,否则序列化时会报错。(注意:C#的编译器可能会在编译时优化掉对象中没有用到的字段、属性或对象等资源,如果这些没有被标记为“[Serializable]”的资源被优化时删掉,则不会出现异常。)

 

经验:
.Net 框架中一般是那些用来存放数据的类型才能被序列化。

 

细节:
方法 object.ReferenceEquals(变量1,变量2) 可以用来判断变量 1 和变量 2 是否引用(指向)同一个引用。
方法 object.Equals(变量1,变量2) 可以用来判断变量 1 和变量 2 的值或对象的所有成员是否相等。

 

细节:
深度拷贝:两个对象完全相同,但两者完全分离,没有共享的“指向”或“引用”部分。
浅层拷贝:两个对象完全相同,但两者不是完全分离,有一部分(或全部)是两者“共用的”(即两个对象的某些成员共同引用同一个对象)。

 

细节:
如果对象没有标记为[Serializable](可序列化),那么要进行拷贝的话只能一个字段的手工拷贝。如:Person p1 = new Person(); p1.Name = p.Name; p1.Age = p.Age; p1.Pet = p.Pet......。

 

细节:
可以通过实现 ICloneable 接口来实现浅层拷贝的功能。在实现 ICloneable 接口的 Clone 方法时本质上还是通过“对对象的每个字段或属性进行逐个赋值给一个新对象的成员”来实现对象拷贝的效果。

 

技巧:
使用 object.MemberwiseClone() 方法可以自动地为对象进行成员级别的逐个拷贝工作,对值类型的成员进行值拷贝操作(普通的“值传递”拷贝方式),而对引用类型的成员则进行引用拷贝操作(“引用传递”的拷贝方式,即把新对象的该成员指向旧对象的相应成员指向的对象,而不是把该旧对象成员的对象完完全全地分离式地拷贝一份给新对象的成员)。
简单来说,object.MemberwiseClone() 方法执行的是“浅拷贝”而非“深拷贝”。
另外,该方法是 protected 级别的方法,只能内部调用。

 

细节:
Person p1 = new Person();
Person p2 = p1;  //千万不要以为这就是拷贝的过程,执行这行代码只是“让 p2 指向 p1 所指向的对象”而已,没有进行任何的拷贝操作。

 

细节:
若要自己写一个 dll,可以创建一个“类库”项目。

 

细节:
无论是 .exe 文件,还是 .dll 文件,都是一个 Assembly(程序集)。

 

重点:
类(class)类型的默认访问级别为 internal,
internal 的可访问范围:该类所在的 Assembly(程序集)中可以被访问,即使是该 Assembly 里的其他 namespace 下的类都可以调用该类,出了 Assembly(程序集)以外的代码调用不了该类。

 

区别:
1. 类库项目会生成 dll,这里的 dll 和 windows 中普通的 dll 虽然都是 dll,但两者不一样,几乎没有任何关系,类库项目生成的非 windows 下的 dll 是特殊的 dll,被称作 程序集(Assembly)。
2. 在 .Net 中,exe 也可以看作是类库,也可以引用,.Net 的 exe 也是 Assembly。
3. .Net 中的 exe 和 dll 的区别就是在于:exe 中包含入口函数,其他没有区别,exe 也可以当成 dll 那样引用、也可以反编译。

 

经验:
如果项目继续,确实要访问 internal 成员、private 成员,用反射就可以实现。

 

细节:
GAC:全局程序集缓存。 .Net 框架下的所有 dll 都是放在 GAC 中的,被添加到 GAC 中的 dll 都是全局的,不需要通过“把 dll 拷贝到每个需要引用该 dll 的项目下”来实现引用。

 

经验:
写程序时需要遵守这样的规矩:变量(参数、返回值等)的类型能用父类就不要用子类,能用接口就不要用类。
如下面代码所示:

 1 namespace 接口入门1
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 //能用接口就不要用类。
8 List<int> list = new List<int>();//不推荐
9 IList<int> list2 = new List<int>();//推荐
10
11 //能用父类就不要用子类。
12 Person p1 = new Chinese();//推荐
13 Chinese p2 = new Chinese();//不推荐
14
15 Console.ReadKey();
16 }
17 }
18
19 abstract class Person //抽象类,不能被实例化。
20 {
21 public abstract void SayHello(); //抽象方法。
22 }
23
24 class Chinese : Person //继承自抽象类 Person
25 {
26 public override void SayHello()
27 {
28 Console.WriteLine("你好");
29 }
30 }
31 }

 

总结:
“世界上本没有模式,用的人多了,也就有了模式”,模式只是人们总结出来的经验而已,并不是什么高深理念。

 

细节:
在接口中也可以声明属性,因为属性本质上也是方法。

 

重点:
设计模式中的一个重要原则:隔离变化。

 

重点中的重点:
委托和接口的区别:
1. 委托只能有一个方法,因为委托就是方法的指针,如果待定的代码用一个函数就可以搞定,用委托会比较合适;
2. 接口可以定义多个成员(属性、方法),如果待定的代码需要多个函数来实现,则用接口比较好。
3. 一般情况下,能用接口就用接口,委托一般只是在定义事件的时候用。
4. 委托就是面向过程版的接口,接口就是面向对象版的委托。还没有面向对象的语言的时候,就是用委托(函数指针)来实现接口的功能的。

 

见识:
VS中的“数据库”其实有两种,一种是“基于服务的数据库”(SQL Server),后缀名为“.mdf”,而另一种是“本地数据库”(SQL Server Compact Edition,亦即:SQL Server CE),后缀名为“.sdf”,精简版,一般多用在手机项目中。

 

技巧:
数据库连接字符串的提取方法:
在“服务器资源管理器”中找到并选中要被连接的数据库文件(.mdf,.sdf等的数据库文件),然后右键点击“属性”以打开“属性窗口”,在“属性窗口”中找到“连接字符串”属性,其属性值即为该数据库的连接字符串。

 

细节:
1. SqlCeConnection 和 SqlConnection 都是继承自 IDbConnection 接口的;
2. SqlCeCommand 和 SqlCommand 都是继承自 IDbCommand 接口的;
3. SqlCeDataReader 和 SqlDataReader 都是继承自 IDataReader 接口的;
4. 要使用 SqlCeConnection、SqlCeCommand、SqlCeDataReader 等 SQLServerCE 相关的类,需要“添加引用”,引用的是 .Net 框架下的名为“System.Data.SqlServerCe”的程序集(dll)。
5. 数据库接口 IDbConnection、IDbCommand、和 IDataReder 等都是继承自命名空间 System.Data。

 

细节:
实现接口必须实现接口中所有的方法。

 

经验:
在业务系统方面的开发中“业务变更的灵活性”往往比“运行效率”更重要。

 

区别:
VS2010中的“实现接口”和“显式实现接口”的功能区别:
大多数时候两个功能都是一样的,当出现后面说的这种情况时才需要用“显式实现接口”:假设一个类(A)继承了两个接口 a1 和 a2,而 a1 和 a2 同时都含有名称为“SayHello”的方法签名,此时如果在类 A 中实现两个接口的话会产生命名冲突,此时才需要用“显式实现接口”。

 

经验:
1. 用修饰符 abstract 修饰字段时会报错:“修饰符“abstract”对于字段无效。请尝试改用属性。”。
2. 空的引用对象都可以Cast,例如下面代码:object o = null;string s = (string)o;MessageBox.Show((null == s).ToString());执行该段代码后的输出结果是“True”。

 

重点:
抽象类和子类的执行调用顺序,如下面代码所示:

 1 namespace 配置平台
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 //创建一个继承自抽象类 DbSettingsProvider 的 SqlSettingsProvider2 对象。
8 ISettingsProvider sp = new SqlSettingsProvider2();
9
10 sp["测试"] = "aaa";
11 sp["FTP地址"] = "127.0.0.1";
12 sp["FTP用户名"] = "admin";
13 sp["FTP密码"] = "123";
14
15 foreach (string s in sp.Names)
16 {
17 Console.WriteLine(s);
18 }
19 Console.ReadKey();
20 }
21 }
22
23 //实现接口 ISettingsProvider 的抽象类 DbSettingsProvider 。
24 abstract class DbSettingsProvider : ISettingsProvider
25 {
26
27 abstract protected IDbConnection CreateConnection(); //标识为抽象类,强制让子类去实现这个方法。模板方法模式。
28
29 public string this[string name]
30 {
31 get
32 {
33 using (IDbConnection conn = CreateConnection()) //让子类去实现这个方法。模板方法模式。
34 {
35 。。。。。。
36 }
37 }
38 set
39 {
40 using (IDbConnection conn = CreateConnection()) //让子类去实现这个方法。模板方法模式。
41 {
42 。。。。。。
43 }
44 }
45 }
46
47 public string[] Names
48 {
49 get
50 {
51 using (IDbConnection conn = CreateConnection()) //让子类去实现这个方法。模板方法模式。
52 {
53 。。。。。。
54 }
55 }
56 }
57
58 public bool NameExists(string name)
59 {
60 return Names.Contains(name);
61 }
62 }
63
64 //继承自抽象类 DbSettingsProvider 的非抽象子类 SqlSettingsProvider2。
65 class SqlSettingsProvider2 : DbSettingsProvider //继承自抽象类 DbSettingsProvider ,并实现抽象类中的抽象 CreateConnection 成员方法。
66 {
67 protected override System.Data.IDbConnection CreateConnection()
68 {
69 return new SqlConnection(ConnStr);
70 }
71 }
72 }

代码执行顺序:当代码执行到“sp["测试"] = "aaa";”时,先到非抽象子类 SqlSettingsProvider2 中找索引器,由于索引器是通过继承抽象的父类 DbSettingsProvider 来实现的,所以到抽象父类 DbSettingsProvider 中执行非抽象的索引器部分的代码,当在父类 DbSettingsProvider 的非抽象代码要调用到父类自身的抽象成员时,就会返回到该子类里寻找父类中抽象成员在该子类的对应 CreateConnection() 方法实现。

 

经验:
抽象类的特点:
抽象类相对于接口来说,抽象性较低,它不像接口那样,要求其中的每个成员都要在子类中实现,抽象类本身可以实现部分的成员,可以把各个子类中的共同部分写在抽象的父类中,只把父类中决定不了的方法或属性抽象出来让各个子类完成各自的特殊实现,以便更好地复用代码。

 

细节:
1. Assembly 类就是对 Assembly 的描述。
2. Assembly 类的对象(asm)的 asm.CodeBase 属性返回的是当前的 asm 对象所在的路径。
3. Assembly.Load()(再早期是用 Assembly.LoadWithPartialName("Assembly的名称") ,现在被标记为“已过时”)是根据 Assembly 的不包含后缀名的名称(项目→属性→应用程序→程序集名称)获得 Assembly。
4. 注意!!!用上面第 3 点的方法获得 Assembly 的前提是:该 Assembly 必须已经被项目中的“引用”引用进来,否则仍然会报异常。如果不想以这种方式引用 Assembly ,想直接引用某个文件夹下的 Assembly 的话,可以用 “Assembly.LoadFile("文件全路径")”

 

细节:
获得 Assembly 的方法:
1. AppDomain.CurrentDomain.GetAssemblies();
2. Assembly.Load("程序集名称") ,如:Assembly. Load("MyClassLib1");(该方式要求在调用前已经在项目中引用该 Assembly)。
3. Assembly.LoadFile("程序集对应的文件的文件名"),如:Assembly.LoadFile(@"D:\MyClassLib1.dll");

 

区别:
1. 和 Assembly.Load("MyClassLib1") 不一样,Assembly.LoadFile(@"D:\MyClassLib1.dll") 是从文件中加载Assembly,不需要在编译的时候引用。
2. 传给Assembly.LoadFile()方法的参数的字符串不是程序集(Assembly)的名称,而是程序集(Assembly)对应的dll后缀文件的名称(如:应传进“Assembly.dll”,而不是“Assembly”,此处的“Assembly”是程序集的名称,“Assembly.dll”则是程序集对应的文件的名称。)

 

获得Type对象的方法:
1. 通过类获得类的 Type:Type t = typeof(Person)
2. 通过对象获得该对象所属的类的 Type:Type t = person.GetType()
3. 通过类的FullName获得类的 Type:Type t = Type.GetType("System.Guid")。这种方法只能获得当前代码所在 Assembly 或者 mscorlib 中的类
4. 调用 Assembly 的 GetExportedTypes() 方法可以得到 Assembly 中定义的所有的 public 类型。!!!
5. 调用 Assembly 的 GetTypes() 方法可以得到 Assembly 中定义的所有的类型。
6. 调用 Assembly 的 GetType(name) 方法可以得到 Assembly 中定义的全名为 name 的类型信息。

 

区别:
“对象名.GetType()”和“typeof(类型名)”的区别:
1. 两种方式返回的都是该类型的 Type 对象。
2. 前者需要通过类的对象来返回该类的 Type 对象,而后者只要通过类型名即可返回该类的 Type 对象。

 

理解:
动态创建对象,动态调用对象的方法,这就是反射。

 

细节:
Activator 的静态 CreateInstance(Type 的对象 type) 方法可以动态地根据 type 的类型来创建一个对象。如下面代码所示:

1 object obj = Activator.CreateInstance(type);

 

作用:
通过反射,可以不需要将程序的所有功能都写死在程序中,运行时动态执行插件(dll),这样需要添加新功能的时候只要按照约定的规范(例如“更新的 dll 是放到C:\Plugin\个文件夹下”,“文件中有哪些方法哪些参数”,“啥时候调用啥方法”等之类的自定义规则)编写插件,将插件的 dll 放到插件目录下就可以实现“更新或添加功能”了。

 

经验:
反射还会在后面的“如鹏网”项目中用在实现“多种第三方支付方式”的功能。

 

细节:
Type 类型对象 type 的 IsAssignableFrom 方法可以用来判断一个类是否实现了一个接口。

 

细节:
[Obsolete("过时提示信息")] 特性能让使引用该方法的地方有警告提示,提示信息为“已过时:”+“该特性中传入的字符串内容”。

 

原理:
特性的本质只是相当于一个“标签”,“特性自身能起什么作用”是它自己没法决定的,这“起什么作用”是由解析者来决定并实现的,而这个解析者的角色在 .NET 环境下可以看作就是 C# 的编译器。