答:
new 修饰符与 new 操作符是两个概念
new 修饰符用于声明类或类的成员,表示隐藏了基类中同名的成员。而new 操作符用于实例化一个类型
new 修饰符只能用于继承类,一般用于弥补基类设计的不足
new 修饰符和 override 修饰符不可同时用在一个成员上,因为这两个修饰符在含义上互相排斥
示例:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace Example09
6{
7class BaseClass
8![]()
{
9//基类设计者声明了一个PI的公共变量,方便进行运算
10public static double PI = 3.1415;
11}
12class DervieClass : BaseClass
13![]()
{
14//继承类发现该变量的值不能满足运算精度,于是可以通过new修饰符显式隐藏基类中的声明
15public new static double PI = 3.1415926;
16}
17class Program
18![]()
{
19static void Main(string[] args)
20![]()
{
21Console.WriteLine(BaseClass.PI);
22Console.WriteLine(DervieClass.PI);
23![]()
24Console.ReadLine();
25}
26}
27}
结果:
3.1415
3.1415926
10.this 关键字的含义?
答:
this 是一个保留字,仅限于构造函数和方法成员中使用
在类的构造函数中出现表示对正在构造的对象本身的引用,在类的方法中出现表示对调用该方法的对象的引用,在结构的构造上函数中出现表示对正在构造的结构的引用,在结构的方法中出现表示对调用该方法的结果的引用
this 保留字不能用于静态成员的实现里,因为这时对象或结构并未实例化
在 C# 系统中,this 实际上是一个常量,所以不能使用 this++ 这样的运算
this 保留字一般用于限定同名的隐藏成员、将对象本身做为参数、声明索引访问器、判断传入参数的对象是否为本身
示例:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace Example10
6{
7class Class1
8![]()
{
9private double c;
10private string value;
11![]()
12public double C
13![]()
{
14get
15![]()
{
16return c;
17}
18}
19public Class1(double c)
20![]()
{
21//限定同名的隐藏成员
22this.c = c;
23}
24public Class1(Class1 value)
25![]()
{
26//用对象本身实例化自己没有意义
27if (this != value)
28![]()
{
29c = value.C;
30}
31}
32public override string ToString()
33![]()
{
34//将对象本身做为参数
35return string.Format("{0} Celsius = {1} Fahrenheit", c, UnitTransClass.C2F(this));
36}
37![]()
38//由于好奇,在这做了一个效率测试,想看看到底哪种方式访问成员变量更快,结论:区别不大。。。
39public string Test1()
40![]()
{
41long vTickCount = Environment.TickCount;
42for (int i = 0; i < 10000000; i++)
43this.value = i.ToString();
44return string.Format("Have this.: {0} MSEL", Environment.TickCount - vTickCount);
45}
46public string Test2()
47![]()
{
48long vTickCount = Environment.TickCount;
49for (int i = 0; i < 10000000; i++)
50value = i.ToString();
51return string.Format("Don't have this.: {0} MSEL", Environment.TickCount - vTickCount);
52}
53}
54class UnitTransClass
55![]()
{
56public static double C2F(Class1 value)
57![]()
{
58//摄氏到华氏的转换公式
59return 1.8 * value.C + 32;
60}
61}
62class Program
63![]()
{
64static void Main(string[] args)
65![]()
{
66Class1 tmpObj = new Class1(37.5);
67![]()
68Console.WriteLine(tmpObj);
69![]()
70Console.WriteLine(tmpObj.Test1());
71Console.WriteLine(tmpObj.Test2());
72![]()
73Console.ReadLine();
74}
75}
76}
结果:
37.5 Celsius = 99.5 Fahrenheit
Have this.: 4375 MSEL
Don't have this.: 4406 MSEL
11.可以使用抽象函数重写基类中的虚函数吗?
答:
可以
需使用 new 修饰符显式声明,表示隐藏了基类中该函数的实现
或增加 override 修饰符,表示抽象重写了基类中该函数的实现
示例:
1class BaseClass
2{
3public virtual void F()
4![]()
{
5Console.WriteLine("BaseClass.F");
6}
7}
8abstract class DeriveClass1 : BaseClass
9{
10public abstract new void F();
11}
12
13//感谢watson hua(http://huazhihao.cnblogs.com/)的指点
14//是他提醒了我还可以用这种方法抽象重写基类的虚方法
15abstract class DeriveClass2 : BaseClass
16{
17public abstract override void F();
18}
12.密封类可以有虚函数吗?答:
可以,基类中的虚函数将隐式的转化为非虚函数,但密封类本身不能再增加新的虚函数
示例:
1class BaseClass
2{
3public virtual void F()
4![]()
{
5Console.WriteLine("BaseClass.F");
6}
7}
8sealed class DeriveClass : BaseClass
9{
10//基类中的虚函数F被隐式的转化为非虚函数
11
12//密封类中不能再声明新的虚函数G
13//public virtual void G()
14//{
15// Console.WriteLine("DeriveClass.G");
16//}
17}
13.什么是属性访问器?答:
属性访问器(Property Accessor),包括 get 访问器和 set 访问器分别用于字段的读写操作
其设计目的主要是为了实现面向对象(OO)中的封装思想。根据该思想,字段最好设为private,一个精巧的类最好不要直接把字段设为公有提供给客户调用端直接访问
另外要注意属性本身并不一定和字段相联系
14.abstract 可以和 virtual 一起使用吗?可以和 override 一起使用吗?答:
abstract 修饰符不可以和 static、virtual 修饰符一起使用
abstract 修饰符可以和 override 一起使用,参见第11点
示例:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace Example14
6{
7class BaseClass
8![]()
{
9public virtual void F()
10![]()
{
11Console.WriteLine("BaseClass.F");
12}
13}
14abstract class DeriveClass1 : BaseClass
15![]()
{
16//在这里, abstract是可以和override一起使用的
17public abstract override void F();
18}
19class Program
20![]()
{
21static void Main(string[] args)
22![]()
{
23}
24}
25}
15.接口可以包含哪些成员?答:
接口可以包含属性、方法、索引指示器和事件,但不能包含常量、域、操作符、构造函数和析构函数,而且也不能包含任何静态成员
16.类和结构的区别?
答:
类:类是引用类型在堆上分配,类的实例进行赋值只是复制了引用,都指向同一段实际对象分配的内存
类有构造和析构函数
类可以继承和被继承
结构:
结构是值类型在栈上分配(虽然栈的访问速度比较堆要快,但栈的资源有限放),结构的赋值将分配产生一个新的对象。
结构没有构造函数,但可以添加。结构没有析构函数
结构不可以继承自另一个结构或被继承,但和类一样可以继承自接口
示例:
根据以上比较,我们可以得出一些轻量级的对象最好使用结构,但数据量大或有复杂处理逻辑对象最好使用类。
如:Geoemtry(GIS 里的一个概论,在 OGC 标准里有定义) 最好使用类,而 Geometry 中点的成员最好使用结构
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace Example16
6{
7interface IPoint
8![]()
{
9double X
10![]()
{
11get;
12set;
13}
14double Y
15![]()
{
16get;
17set;
18}
19double Z
20![]()
{
21get;
22set;
23}
24}
25//结构也可以从接口继承
26struct Point: IPoint
27![]()
{
28private double x, y, z;
29//结构也可以增加构造函数
30public Point(double X, double Y, double Z)
31![]()
{
32this.x = X;
33this.y = Y;
34this.z = Z;
35}
36public double X
37![]()
{
38get
{ return x; }
39set
{ x = value; }
40}
41public double Y
42![]()
{
43get
{ return x; }
44set
{ x = value; }
45}
46public double Z
47![]()
{
48get
{ return x; }
49set
{ x = value; }
50}
51}
52//在此简化了点状Geometry的设计,实际产品中还包含Project(坐标变换)等复杂操作
53class PointGeometry
54![]()
{
55private Point value;
56![]()
57public PointGeometry(double X, double Y, double Z)
58![]()
{
59value = new Point(X, Y, Z);
60}
61public PointGeometry(Point value)
62![]()
{
63//结构的赋值将分配新的内存
64this.value = value;
65}
66public double X
67![]()
{
68get
{ return value.X; }
69set
{ this.value.X = value; }
70}
71public double Y
72![]()
{
73get
{ return value.Y; }
74set
{ this.value.Y = value; }
75}
76public double Z
77![]()
{
78get
{ return value.Z; }
79set
{ this.value.Z = value; }
80}
81public static PointGeometry operator +(PointGeometry Left, PointGeometry Rigth)
82![]()
{
83return new PointGeometry(Left.X + Rigth.X, Left.Y + Rigth.Y, Left.Z + Rigth.Z);
84}
85public override string ToString()
86![]()
{
87return string.Format("X: {0}, Y: {1}, Z: {2}", value.X, value.Y, value.Z);
88}
89}
90class Program
91![]()
{
92static void Main(string[] args)
93![]()
{
94Point tmpPoint = new Point(1, 2, 3);
95![]()
96PointGeometry tmpPG1 = new PointGeometry(tmpPoint);
97PointGeometry tmpPG2 = new PointGeometry(tmpPoint);
98tmpPG2.X = 4;
99tmpPG2.Y = 5;
100tmpPG2.Z = 6;
101![]()
102//由于结构是值类型,tmpPG1 和 tmpPG2 的坐标并不一样
103Console.WriteLine(tmpPG1);
104Console.WriteLine(tmpPG2);
105![]()
106//由于类是引用类型,对tmpPG1坐标修改后影响到了tmpPG3
107PointGeometry tmpPG3 = tmpPG1;
108tmpPG1.X = 7;
109tmpPG1.Y = 8;
110tmpPG1.Z = 9;
111Console.WriteLine(tmpPG1);
112Console.WriteLine(tmpPG3);
113![]()
114Console.ReadLine();
115}
116}
117}
结果:
X: 1, Y: 2, Z: 3
X: 4, Y: 5, Z: 6
X: 7, Y: 8, Z: 9
X: 7, Y: 8, Z: 9
17.接口的多继承会带来哪些问题?答:
C# 中的接口与类不同,可以使用多继承,即一个子接口可以有多个父接口。但如果两个父成员具有同名的成员,就产生了二义性(这也正是 C# 中类取消了多继承的原因之一),这时在实现时最好使用显式的声明
示例:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace Example17
6{
7class Program
8![]()
{
9//一个完整的接口声明示例
10interface IExample
11![]()
{
12//属性
13string P
14![]()
{
15get;
16set;
17}
18//方法
19string F(int Value);
20//事件
21event EventHandler E;
22//索引指示器
23string this[int Index]
24![]()
{
25get;
26set;
27}
28}
29interface IA
30![]()
{
31int Count
{ get; set;}
32}
33interface IB
34![]()
{
35int Count();
36}
37//IC接口从IA和IB多重继承
38interface IC : IA, IB
39![]()
{
40}
41class C : IC
42![]()
{
43private int count = 100;
44//显式声明实现IA接口中的Count属性
45int IA.Count
46![]()
{
47get
{ return 100; }
48set
{ count = value; }
49}
50//显式声明实现IB接口中的Count方法
51int IB.Count()
52![]()
{
53return count * count;
54}
55}
56static void Main(string[] args)
57![]()
{
58C tmpObj = new C();
59![]()
60//调用时也要显式转换
61Console.WriteLine("Count property: {0}", ((IA)tmpObj).Count);
62Console.WriteLine("Count function: {0}", ((IB)tmpObj).Count());
63![]()
64Console.ReadLine();
65}
66}
67}
结果:
Count property: 100
Count function: 10000
18.抽象类和接口的区别?答:
抽象类(abstract class)可以包含功能定义和实现,接口(interface)只能包含功能定义
抽象类是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性
分析对象,提炼内部共性形成抽象类,用以表示对象本质,即“是什么”
为外部提供调用或功能需要扩充时优先使用接口
19.别名指示符是什么?答:
通过别名指示符我们可以为某个类型起一个别名
主要用于解决两个命名空间内有同名类型的冲突或避免使用冗余的命名空间
别名指示符在所有命名空间最外层定义,作用域为整个单元文件。如果定义在某个命名空间内,那么它只在直接隶属的命名空间内起作用
示例:
Class1.cs:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01
6{
7class Class1
8![]()
{
9public override string ToString()
10![]()
{
11return "com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01's Class1";
12}
13}
14}
Class2.cs:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02
6{
7class Class1
8![]()
{
9public override string ToString()
10![]()
{
11return "com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02's Class1";
12}
13}
14}
主单元(Program.cs):
1using System;
2using System.Collections.Generic;
3using System.Text;
4
5![]()
6//使用别名指示符解决同名类型的冲突
7//在所有命名空间最外层定义,作用域为整个单元文件
8using Lib01Class1 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
9using Lib02Class2 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02.Class1;
10![]()
11namespace Example19
12{
13namespace Test1
14![]()
{
15//Test1Class1在Test1命名空间内定义,作用域仅在Test1之内
16using Test1Class1 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
17![]()
18class Class1
19![]()
{
20//Lib01Class1和Lib02Class2在这可以正常使用
21Lib01Class1 tmpObj1 = new Lib01Class1();
22Lib02Class2 tmpObj2 = new Lib02Class2();
23//TestClass1在这可以正常使用
24Test1Class1 tmpObj3 = new Test1Class1();
25}
26}
27namespace Test2
28![]()
{
29using Test1Class2 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
30![]()
31class Program
32![]()
{
33static void Main(string[] args)
34![]()
{
35//Lib01Class1和Lib02Class2在这可以正常使用
36Lib01Class1 tmpObj1 = new Lib01Class1();
37Lib02Class2 tmpObj2 = new Lib02Class2();
38![]()
39//注意这里,TestClass1在这不可以正常使用。
40//因为,在Test2命名空间内不能使用Test1命名空间定义的别名
41//Test1Class1 tmpObj3 = new Test1Class1();
42![]()
43//TestClass2在这可以正常使用
44Test1Class2 tmpObj3 = new Test1Class2();
45![]()
46Console.WriteLine(tmpObj1);
47Console.WriteLine(tmpObj2);
48Console.WriteLine(tmpObj3);
49![]()
50Console.ReadLine();
51}
52}
53}
54}
结果:
com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01's Class1
com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02's Class1
com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01's Class120.如何手工释放资源?
答:
.NET 平台在内存管理方面提供了GC(Garbage Collection),负责自动释放托管资源和内存回收的工作。但在以下两种情况需要我们手工进行资源释放:一、由于它无法对非托管资源进行释放,所以我们必须自己提供方法来释放对象内分配的非托管资源,比如你在对象的实现代码中使用了一个COM对象;二、你的类在运行是会产生大量实例(象 GIS 中的Geometry),必须自己手工释放这些资源以提高程序的运行效率
最理想的办法是通过实现一个接口显式的提供给客户调用端手工释放对象,System 命名空间内有一个 IDisposable 接口,拿来做这事非常合适,省得我们自己再声明一个接口了示例:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace Example20
6{
7class Program
8![]()
{
9class Class1 : IDisposable
10![]()
{
11//析构函数,编译后变成 protected void Finalize(),GC会在回收对象前会调用调用该方法
12~Class1()
13![]()
{
14Dispose(false);
15}
16![]()
17//通过实现该接口,客户可以显式地释放对象,而不需要等待GC来释放资源,据说那样会降低效率
18void IDisposable.Dispose()
19![]()
{
20Dispose(true);
21}
22![]()
23//将释放非托管资源设计成一个虚函数,提供在继承类中释放基类的资源的能力
24protected virtual void ReleaseUnmanageResources()
25![]()
{
26//Do something...
27}
28![]()
29//私有函数用以释放非托管资源
30private void Dispose(bool disposing)
31![]()
{
32ReleaseUnmanageResources();
33![]()
34//为true时表示是客户显式调用了释放函数,需通知GC不要再调用对象的Finalize方法
35//为false时肯定是GC调用了对象的Finalize方法,所以没有必要再告诉GC你不要调用我的Finalize方法啦
36if (disposing)
37![]()
{
38GC.SuppressFinalize(this);
39}
40}
41}
42static void Main(string[] args)
43![]()
{
44//tmpObj1没有手工释放资源,就等着GC来慢慢的释放它吧
45Class1 tmpObj1 = new Class1();
46![]()
47//tmpObj2调用了Dispose方法,传说比等着GC来释放它效率要调一些
48//个人认为是因为要逐个对象的查看其元数据,以确认是否实现了Dispose方法吧
49//当然最重要的是我们可以自己确定释放的时间以节省内存,优化程序运行效率
50Class1 tmpObj2 = new Class1();
51((IDisposable)tmpObj2).Dispose();
52}
53}
54}
21.P/Invoke是什么?答:
在受控代码与非受控代码进行交互时会产生一个事务(transition) ,这通常发生在使用平台调用服务(Platform Invocation Services),即P/Invoke
如调用系统的 API 或与 COM 对象打交道,通过 System.Runtime.InteropServices 命名空间
虽然使用 Interop 非常方便,但据估计每次调用事务都要执行 10 到 40 条指令,算起来开销也不少,所以我们要尽量少调用事务
如果非用不可,建议本着一次调用执行多个动作,而不是多次调用每次只执行少量动作的原则
22.StringBuilder 和 String 的区别?
答:
String 在进行运算时(如赋值、拼接等)会产生一个新的实例,而 StringBuilder 则不会。所以在大量字符串拼接或频繁对某一字符串进行操作时最好使用 StringBuilder,不要使用 String
另外,对于 String 我们不得不多说几句:
1.它是引用类型,在堆上分配内存
2.运算时会产生一个新的实例
3.String 对象一旦生成不可改变(Immutable)
3.定义相等运算符(== 和 !=)是为了比较 String 对象(而不是引用)的值
示例:
1using System;
2using System.Collections.Generic;
3using System.Text;
4![]()
5namespace Example22
6{
7class Program
8![]()
{
9static void Main(string[] args)
10![]()
{
11const int cycle = 10000;
12![]()
13long vTickCount = Environment.TickCount;
14String str = null;
15for (int i = 0; i < cycle; i++)
16str += i.ToString();
17Console.WriteLine("String: {0} MSEL", Environment.TickCount - vTickCount);
18![]()
19vTickCount = Environment.TickCount;
20//看到这个变量名我就生气,奇怪为什么大家都使它呢? :)
21StringBuilder sb = new StringBuilder();
22for (int i = 0; i < cycle; i++)
23sb.Append(i);
24Console.WriteLine("StringBuilder: {0} MSEL", Environment.TickCount - vTickCount);
25![]()
26string tmpStr1 = "A";
27string tmpStr2 = tmpStr1;
28Console.WriteLine(tmpStr1);
29Console.WriteLine(tmpStr2);
30//注意后面的输出结果,tmpStr1的值改变并未影响到tmpStr2的值
31tmpStr1 = "B";
32Console.WriteLine(tmpStr1);
33Console.WriteLine(tmpStr2);
34![]()
35Console.ReadLine();
36}
37}
38}
结果:
String: 375 MSEL
StringBuilder: 16 MSEL
A
A
B
A