学习交流,欢迎转载。转载请注明文章来源:http://www.cnblogs.com/lgjspace/archive/2011/10/12/2214013.html
区别:
程序员自行用代码方式实现的回收和 GC 方式的回收的区别:
前者(代码方式)回收效率高,有针对性,但一旦程序员忘了实现回收代码,会出现不可预期的错误,安全性不高;
而后者(GC 方式)能在一定程度上解放了程序员的精力,可以不用总是要顾虑着内存的回收,但回收效率比不上代码方式的回收,GC 的回收不是实时的回收,回收的时机是由 GC 的内部算法决定的。
注意:
这里说的垃圾回收(GC)主要是对对象占用的堆(托管堆)中的内存资源来说的,非托管堆及栈中的资源 GC 是不会干涉的,GC只能回收托管的内存资源。其中,GC 不干涉的两个空间(非托管堆和栈)之中,栈里的资源一旦出了作用域后就会自动清除,不用我们管。
细节:
Person p1 = new Person();
Person p2 = p1; //让 p2 指向 p1 所指向的对象
注:这里有两个 Person 变量,但只有一个 Person 对象。只有“new”出来的才是对象,变量只是用来接载对象的容器。
细节:
可以被 GC 回收的对象所要达到的条件就是:该对象没有被任何变量指向自己。
注意:
虽然可以手动调用 GC 来回收资源,但不建议随意调用,否则会造成系统性能下降。在 .Net 的机制中,对于 GC 在执行回收动作的时候,整个程序是会被暂停的,如果暂停得频繁,会造成程序“很卡”,甚至会更慢。
细节:
GC 只能回收内存资源,对于数据库连接(ADO.Net)、文件句柄、Socket 连接等这些资源(非托管资源,UnManaged Source)就无能为力了,程序员必须要自行控制资源回收。
Close 和 Dispose 关系:
1.在继承 IDisposable 接口的类中,潜规则默认都会自己写上一个 Close 方法,但该方法不是 IDisposable 接口的成员,不要求必须实现 Close 方法,只是建议而已,有的类可能没有 Close 方法。
2.同时,潜规则也默认如果继承 IDisposable 接口的类中含有 Close 方法(或者功能类似于 Close 方法而名字不是 Close 的方法)的话,一般在 Dispose 方法中都会“代为”作一下判断,判断一下该对象在执行本 Dispose 方法之前是否已经被 Close 掉,如果还没被 Close 掉则在此处为对象先“代为” Close 一下再进行 Dispose,如果对象已经被 Close 掉则直接跳过该“代 Close”的操作,进入 Dispose 的操作。
3.但这种“在 Dispose 方法中代理 Close ”的做法也只是推荐,没有硬性规定,甚至还可以直接没有提供 Close 方法,因为 IDisposable 接口的 Dispose 方法根本不理会到底有没有 Close 的存在。
4.一般情况下的 Close 的功能只是“关闭”,可能关闭之后还可以被再次打开,但是 Dispose 一般就表示资源被回收了,再也没法重用。
重点:
值类型是复制传递的,引用类型是引用传递的。int、bool、DateTime 等都是值类型,普通的对象则是引用类型,String 看上去像值类型,实际上却是引用类型,所有的数组也是引用类型。
细节:
1.栈(Stack)内存:由编译器负责内存的申请、释放,不用程序员以及 GC 来干涉。局部变量、函数参数等的值类型部分的变量一般都是在栈内存中,甚至对象的变量名也是放在栈内存中的。
2.堆(Heap)内存:由 GC、程序员负责创建、释放的资源。引用类型,static 变量,类变量等一般都是在堆内存中。static、readonly、const 三种“常量”在内存的区域是有区别的。
细节:
String 对象一旦被声明了就不能被再次改变,就算是“看似可以改变”的类似于“s = s.Replace("b","w");”的这种操作都不例外,就以该语句为例,执行该语句实际上是把变量 s 的字符串拷贝一份出来(此时变量 s 里的字符串没有任何改变),然后再对拷贝出来的字符串作 Replace("b","w") 处理,处理完后再把该处理完的字符串重新赋值给装载着原来未被修改的字符串的变量 s,这过程中,变量 s 先是“解除”了自己和源字符串的绑定(指向)关系,然后再和新的被修改后的字符串绑定,相当于指向了新字符串。整个过程中源字符串都没有被改变过,也不能被改变,只是被丢弃了而已。
注意:
代码如下:
string s1 = "a";
string s2 = "b";
string s3 = "c";
string s4 = s1 + s2 + s3;
1. 当执行到最后一行代码时,程序首先会执行“s1 + s2”,产生出一个新的字符串对象,该字符串对象的值为执行“s1 + s2”之后得出的结果字符串“ab”,然后再让该对象和 s3 相加,即相当于执行“"ab" + s3”,此时又产生出一个新对象,然后才赋给变量 s4,所以,执行了这几行语句之后,总共产生了 5 个对象,而不是 4 个,过程中还产生出一个临时的中间字符串对象。
2. 因此,大量的字符串相连会产生大量的中间字符串,字符串是对象,对象的产生是比较慢的,而且如果产生得多的话会占用大量内存。所以要避免大量的字符串的连接操作。
3. 对于 String 的这种缺陷,我们可以用 StringBuilder 来代替 String。StringBuilder 内部实现了字符串的直接拼接之余,不会像 String 这样会造成大量的中间字符串对象,占用内存资源。
4. 因此,在有大量的字符串拼接的操作时推荐用 StringBuilder 来代替 String。
5. 但是,StringBuilder 并不能完全代替掉 String,因为 String 是基本类型,大多数地方都只认 String,不认 StringBuilder,StringBuilder 只是程序中间过程时处理字符串的比较理想的一个工具,处理完后还是要用 ToString() 方法转换回 String类型。
小技巧:
StringBuilder 还可以用类似于“链式编程”的方式来使用 Append() 等方法。
细节:
一般情况下,一个普通类的 ToString() 方法返回的是该类的全名,即类似于“某某某namespace.某某某class”这样的字符串值。
细节:
当父类的成员标记为 virtual 时,子类可以 override 该 virtual 方法。
细节:
对于 object 类型来说,其 Equals() 方法判断是否相等的依据是:如果两个对象是同一个则为 true,否则为 false。这判断条件相当苛刻,可以自己重写(override)父类的 Equals 方法来实现更具实际意义的判断逻辑。
重点:
含有 virtual 和 override 标识的代码的调用顺序:
实例代码如下(计算各种数据类型的“最大值”):
1 namespace CalcMaxValue
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 MaxCaculator c = new IntMaxCaculator();//学了反射就明白为什么这句话不是闲着没事干了
8 object max = c.GetMax(new object[] { 3, 5, 2, 9, 6 });//调用的是 MaxCaculator 类对象的 GetMax 方法。
9 Console.WriteLine(max);
10 Console.ReadKey();
11 }
12 }
13
14 class MaxCaculator
15 {
16 public object GetMax(object[] objs)
17 {
18 object max = objs[0];
19 foreach (object obj in objs)
20 {
21 if (Compare(obj, max) > 0)//当父类发现自己的 Compare 是 virtual 方法时,父类会先到子类中寻找相应的带 override 的 Compare 方法,如果子类中有相应的 Compare 方法的话就调用子类的实现。
22 {
23 max = obj;
24 }
25 }
26 return max;
27 }
28
29 protected virtual int Compare(object obj1, object obj2)
30 {
31 throw new NotImplementedException();
32 }
33 }
34
35 class IntMaxCaculator : MaxCaculator
36 {
37 protected override int Compare(object obj1, object obj2)
38 {
39 int i1 = (int)obj1;
40 int i2 = (int)obj2;
41 return i1 - i2;
42 }
43 }
44 }
上面的这段代码中,IntMaxCaculator 类继承自 MaxCaculator 类,MaxCaculator 类中有一个标识为 virtual 的虚方法Compare(),而在子类 IntMaxCaculator 中有重写(override)方法 Compare(),并且有其自身与父类不同的实现。
1.在执行程序时,首先创建了一个 IntMaxCaculator 类型的对象,但是使用了一个 MaxCaculator 类型的变量来装载,然后调用了 MaxCaculator 类对象 c 的 GetMax 方法,在 GetMax 方法里头调用了自己所在的 MaxCaculator 类中的 Compare() 方法,此时由于该方法被标识为 virtual ,所以程序会优先去试图查找并调用子类 IntMaxCaculator 中的 override 的 Compare 方法,然后再……。
2.但如果 IntMaxCaculator 类中的 override 的 Compare 方法被注释掉,即在子类没有对父类 MaxCaculator 下被注释为virtual 的 Compare 方法进行重写,在程序执行到“if (Compare(obj, max) > 0);”这一句并开始调用 Compare() 方法的那一步时,同样地首先是找到自己所在的类中的那个标记着 virtual 的父类 Compare() 方法,同样地也是因为该方法被标记为 virtual,所以程序会先去寻找子类中有没有该 virtual 方法的重写(override)实现方法,此时由于子类 IntMaxCaculator 中没有重写(override)父类的 Compare() 方法,所以程序会直接调用父类中现有的带 virtual 的 Compare() 方法,然后……。
简要总结上面的代码运行过程原理就是:被 virtual 的父类方法只要有子类的 override 实现就优先执行子类的 override 实现,如果子类中没有相应的 override 实现才执行父类的 virtual 实现。(这也是多态的效果。)
注意!!!:
如果类 c 继承自类 b,而类 b 又继承自类 a,在类 a 中有 virtual 方法 abc(),在类 b 中 override 了父类 a 的 virtual 方法 abc(),如果此时调用了 c 类对象 c1 的 abc() 方法的话,由于 c 类没有重写(override) abc() 方法,此时程序会调用的是 b 类的 abc() 方法,而不是 a 类的 abc() 方法,因为 b 是 c 的直接父类。
技巧:
在 VisualStudio 中,如果一个类(子类)继承了另一个类(父类),而且想 override 父类的 virtual 方法的话,在子类中需要写 override 方法处直接键盘敲“override”加上空格,这样的话VS的自动完成功能就会列出父类的所有可以被 override 的方法或属性等成员,选中其中一个需要 override 的成员,VS就会立刻帮你完成了 override 方法的格式代码(即函数头和函数的大括号这些代码结构框架)了。
区别:
new 方式和 virtual、override 方式的区别:
建议尽量不要用“恶心的new”,这是面向对象的破坏者。
以下面的代码为例:
1 namespace 恶心的New
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 //父类 Person 没有说 SayHello 可以 override,子类偷偷的隐藏了父类的实现,用自己的 Chinese 类型调用没关系,但是一旦用 Person 类型变量调用,让“父类”看到了自身有不带 virtual 的 SayHello 方法的实现,父类就直接用自己的实现而不再管你子类的实现了。
8 Person p1 = new Chinese(); //用父类 Person 的变量装载子类 Chinese 的对象。
9 p1.SayHello();
10
11 Chinese p2 = new Chinese(); //用子类 Chinese 的变量装载相同类型的子类 Chinese 对象。
12 p2.SayHello();
13
14 Person p3 = new Person(); //用父类 Person 的变量装载相同类型的父类 Person 对象。
15 p3.SayHello();
16
17 Console.ReadKey();
18 }
19 }
20
21 class Person
22 {
23 public void SayHello()
24 {
25 Console.WriteLine("我是地球人");
26 }
27 }
28
29 class Chinese : Person
30 {
31 public new void SayHello() //父类不让 override,而子类偏要“override”,“new”出另一个新天地。
32 {
33 Console.WriteLine("我是中国人");
34 }
35 }
36 }
这段代码的执行结果为:
我是地球人
我是中国人
我是地球人
由代码及代码的执行结果可以看出,以 new 的方式来实现“多态”的效果是一种“伪多态”,不是真正的多态。
如果以父类 Person 的变量 p1 来接载 Chinese 对象的话,p1 在调用父类 Person 的 SayHello() 方法时由于该方法没有标识为 virtual,因此不会到子类中查找有没有相应的 override 方法实现,直接就调用该父类的 SayHello() 方法;而如果以子类 Chinese 的变量 p2 来接载 Chinese 对象的话,由于 p2 就是实实在在的 Chinese 子类的对象, 在调用 p2.SayHello() 方法时,如果该方法标识为“new”的话,p2 就不会到父类中调用父类的方法,而是直接调用在子类中被标记为“new”的 SayHello() 方法,即相当于“隐藏了父类的 SayHello() 方法”,形成了“以父类变量装载子类对象,调用子类和父类都有的方法的话,会调用父类的方法;以子类变量装载子类对象,调用子类和父类都有的方法的话,如果子类的方法被标识为“new”,会调用子类的方法”的效果。
但这极不推荐,这是破坏了对象的多态特性。
实现:
单例模式的实现方法:
如下面代码所示:
1 //方法一:
2 namespace TheEarth
3 {
4 class Program
5 {
6 static void Main(string[] args)
7 {
8 //Earth e1 = new Earth();
9 //Earth e2 = new Earth();
10 Earth.Instance.SayHello();
11
12 Console.WriteLine(object.ReferenceEquals(Earth.Instance, Earth.Instance));
13 Console.ReadKey();
14 }
15 }
16
17 class Earth
18 {
19 public readonly static Earth Instance = new Earth();//唯一的一个对象,静态成员只会初始化一次!
20
21 private Earth()
22 {
23 }
24
25 public void SayHello()
26 {
27 Console.WriteLine("我是地球!");
28 }
29 }
30 }
31 //方法二:
32 namespace TheEarth
33 {
34 class Program
35 {
36 static void Main(string[] args)
37 {
38 Earth ear = Earth.GetInstance(); //通过调用 Earth 类的静态 GetInstance() 方法来获取 Earth 类自身维护的唯一实例。
39 ear.SayHello();
40 Console.ReadKey();
41 }
42 }
43
44 class Earth
45 {
46 //定义私有的静态字段
47 private static Earth Instance = null;
48
49 private Earth()
50 {
51 }
52
53 public static Earth GetInstance()
54 {
55 //如果字段是静态字段,则要用“类名.字段名”的方式来调用类的静态字段,
56 //但由于这里是静态字段 Instance 所在的类的内部,因此可以省略类名前缀,直接写字段名即可。
57 if (Instance == null)
58 {
59 Instance = new Earth();
60 }
61 return Instance;
62 }
63
64 public void SayHello()
65 {
66 Console.WriteLine("我是地球!");
67 }
68 }
69 }
区别:
readonly 和 const 的区别:
readonly:运行时常量;
const:编译时常量;
以上两者都为常量,但所处的时机不一样,在 .Net 下的程序项目是先编译后运行的,而且只编译一次,也就是说,const 常量在代码编写时期就必须可以确定,而 readonly 常量则可以在程序每次运行起来的时候才确定它的值。
细节:
内存中的缓存(比如ASP.Net中的Cache)一般都是用 Hashtable、Dictionary 等查找速度非常高的算法类来实现。
细节:
在 File 类中:
GetCreationTime() 方法获取文件的“创建时间”;
GetLastAccessTime() 方法获取文件的“上次访问时间”;GetLastWriteTime() 方法获取文件的“上次修改时间”。
细节:
代码如下:
1 namespace 静态与非静态
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 new Program().SayHello(); //所谓“在静态成员中不能调用非静态方法”指的是“在静态成员中不能不通过一个对象调用非静态方法”,有了对象就能调。
8 }
9
10 public void SayHello()
11 {
12
13 }
14 }
15 }
就如上面的代码注释所述:“所谓“在静态成员中不能调用非静态方法”指的是“在静态成员中不能不通过一个对象调用非静态方法”,有了对象就能调。”
VSS经验:
在多人开发的项目中,分工尽量避免两个人修改同一个文件,尽量每个人编辑不同的文件或是不同的项目。