C#基础概念二十五问(二)

C#高级特性解析
本文深入探讨了C#中的高级特性,包括new修饰符的作用、this关键字的含义、抽象类与接口的区别等,并通过示例代码详细解释了每个概念。
9.new 修饰符是起什么作用?

答:

new 修饰符与 new 操作符是两个概念

new 修饰符用于声明类或类的成员,表示隐藏了基类中同名的成员。而new 操作符用于实例化一个类型

new 修饰符只能用于继承类,一般用于弥补基类设计的不足

new 修饰符和 override 修饰符不可同时用在一个成员上,因为这两个修饰符在含义上互相排斥

示例:

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4 
 5namespace Example09
 6{
 7    class BaseClass
 8    {
 9        //基类设计者声明了一个PI的公共变量,方便进行运算
10        public static double PI = 3.1415;
11    }

12    class DervieClass : BaseClass
13    {
14        //继承类发现该变量的值不能满足运算精度,于是可以通过new修饰符显式隐藏基类中的声明
15        public new static double PI = 3.1415926;
16    }

17    class Program
18    {
19        static void Main(string[] args)
20        {
21            Console.WriteLine(BaseClass.PI);
22            Console.WriteLine(DervieClass.PI);
23 
24            Console.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{
 7    class Class1
 8    {
 9        private double c;
10        private string value;
11 
12        public double C
13        {
14            get
15            {
16                return c;
17            }

18        }

19        public Class1(double c)
20        {
21            //限定同名的隐藏成员
22            this.c = c;
23        }

24        public Class1(Class1 value)
25        {
26            //用对象本身实例化自己没有意义
27            if (this != value)
28            {
29                c = value.C;
30            }

31        }

32        public override string ToString()
33        {
34            //将对象本身做为参数
35            return string.Format("{0} Celsius = {1} Fahrenheit", c, UnitTransClass.C2F(this));
36        }

37 
38        //由于好奇,在这做了一个效率测试,想看看到底哪种方式访问成员变量更快,结论:区别不大。。。
39        public string Test1()
40        {
41            long vTickCount = Environment.TickCount;
42            for (int i = 0; i < 10000000; i++)
43                this.value = i.ToString();
44            return string.Format("Have this.: {0} MSEL", Environment.TickCount - vTickCount);
45        }

46        public string Test2()
47        {
48            long vTickCount = Environment.TickCount;
49            for (int i = 0; i < 10000000; i++)
50                value = i.ToString();
51            return string.Format("Don't have this.: {0} MSEL", Environment.TickCount - vTickCount);
52        }

53    }

54    class UnitTransClass
55    {
56        public static double C2F(Class1 value)
57        {
58            //摄氏到华氏的转换公式
59            return 1.8 * value.C + 32;
60        }

61    }

62    class Program
63    {
64        static void Main(string[] args)
65        {
66            Class1 tmpObj = new Class1(37.5);
67 
68            Console.WriteLine(tmpObj);
69 
70            Console.WriteLine(tmpObj.Test1());
71            Console.WriteLine(tmpObj.Test2());
72 
73            Console.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{
 3    public virtual void F()
 4    {
 5        Console.WriteLine("BaseClass.F");
 6    }

 7}

 8abstract class  DeriveClass1 : BaseClass
 9{
10    public abstract new void F();
11}

12
13//感谢watson hua(http://huazhihao.cnblogs.com/)的指点
14//是他提醒了我还可以用这种方法抽象重写基类的虚方法
15abstract class DeriveClass2 : BaseClass
16{
17    public abstract override void F();
18}
12.密封类可以有虚函数吗?

答:

可以,基类中的虚函数将隐式的转化为非虚函数,但密封类本身不能再增加新的虚函数

示例:

 1class BaseClass
 2{
 3    public virtual void F()
 4    {
 5        Console.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{
 7    class BaseClass
 8    {
 9        public virtual void F()
10        {
11            Console.WriteLine("BaseClass.F");
12        }

13    }

14    abstract class DeriveClass1 : BaseClass
15    {
16        //在这里, abstract是可以和override一起使用的
17        public abstract override void F();
18    }

19    class Program
20    {
21        static 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{
  7    interface IPoint
  8    {
  9        double X
 10        {
 11            get;
 12            set;
 13        }

 14        double Y
 15        {
 16            get;
 17            set;
 18        }

 19        double Z
 20        {
 21            get;
 22            set;
 23        }

 24    }

 25    //结构也可以从接口继承
 26    struct Point: IPoint
 27    {
 28        private double x, y, z;
 29        //结构也可以增加构造函数
 30        public Point(double X, double Y, double Z)
 31        {
 32            this.x = X;
 33            this.y = Y;
 34            this.z = Z;
 35        }

 36        public double X
 37        {
 38            get { return x; }
 39            set { x = value; }
 40        }

 41        public double Y
 42        {
 43            get { return x; }
 44            set { x = value; }
 45        }

 46        public double Z
 47        {
 48            get { return x; }
 49            set { x = value; }
 50        }

 51    }

 52    //在此简化了点状Geometry的设计,实际产品中还包含Project(坐标变换)等复杂操作
 53    class PointGeometry
 54    {
 55        private Point value;
 56        
 57        public PointGeometry(double X, double Y, double Z)
 58        {
 59            value = new Point(X, Y, Z);
 60        }

 61        public PointGeometry(Point value)
 62        {
 63            //结构的赋值将分配新的内存
 64            this.value = value;
 65        }

 66        public double X
 67        {
 68            get { return value.X; }
 69            set { this.value.X = value; }
 70        }

 71        public double Y
 72        {
 73            get { return value.Y; }
 74            set { this.value.Y = value; }
 75        }

 76        public double Z
 77       {
 78            get { return value.Z; }
 79            set { this.value.Z = value; }
 80        }

 81        public static PointGeometry operator +(PointGeometry Left, PointGeometry Rigth)
 82        {
 83            return new PointGeometry(Left.X + Rigth.X, Left.Y + Rigth.Y, Left.Z + Rigth.Z);
 84        }

 85        public override string ToString()
 86        {
 87            return string.Format("X: {0}, Y: {1}, Z: {2}", value.X, value.Y, value.Z);
 88        }

 89    }

 90    class Program
 91    {
 92        static void Main(string[] args)
 93        {
 94            Point tmpPoint = new Point(1, 2, 3);
 95 
 96            PointGeometry tmpPG1 = new PointGeometry(tmpPoint);
 97            PointGeometry tmpPG2 = new PointGeometry(tmpPoint);
 98            tmpPG2.X = 4;
 99            tmpPG2.Y = 5;
100            tmpPG2.Z = 6;
101 
102            //由于结构是值类型,tmpPG1 和 tmpPG2 的坐标并不一样
103            Console.WriteLine(tmpPG1);
104            Console.WriteLine(tmpPG2);
105 
106            //由于类是引用类型,对tmpPG1坐标修改后影响到了tmpPG3
107            PointGeometry tmpPG3 = tmpPG1;
108            tmpPG1.X = 7;
109            tmpPG1.Y = 8;
110            tmpPG1.Z = 9;
111            Console.WriteLine(tmpPG1);
112            Console.WriteLine(tmpPG3);
113 
114            Console.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{
 7    class Program
 8    {
 9        //一个完整的接口声明示例
10        interface IExample
11        {
12            //属性
13            string P
14            {
15                get;
16                set;
17            }

18            //方法
19            string F(int Value);
20            //事件
21            event EventHandler E;
22            //索引指示器
23            string this[int Index]
24            {
25                get;
26                set;
27            }

28        }

29        interface IA
30        {
31            int Count { get; set;}
32        }

33        interface IB
34        {
35            int Count();
36        }

37        //IC接口从IA和IB多重继承
38        interface IC : IA, IB
39        {
40        }

41        class C : IC
42        {
43            private int count = 100;
44            //显式声明实现IA接口中的Count属性
45            int IA.Count
46            {
47                get { return 100; }
48                set { count = value; }
49            }

50            //显式声明实现IB接口中的Count方法
51            int IB.Count()
52            {
53                return count * count;
54            }

55        }

56        static void Main(string[] args)
57        {
58            C tmpObj = new C();
59 
60            //调用时也要显式转换
61            Console.WriteLine("Count property: {0}", ((IA)tmpObj).Count);
62            Console.WriteLine("Count function: {0}", ((IB)tmpObj).Count());
63 
64            Console.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{
 7    class Class1
 8    {
 9        public override string ToString()
10        {
11            return "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{
 7    class Class1
 8    {
 9        public override string ToString()
10        {
11            return "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{
13    namespace Test1
14    {
15        //Test1Class1在Test1命名空间内定义,作用域仅在Test1之内
16        using Test1Class1 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
17 
18        class Class1
19        {
20            //Lib01Class1和Lib02Class2在这可以正常使用
21            Lib01Class1 tmpObj1 = new Lib01Class1();
22            Lib02Class2 tmpObj2 = new Lib02Class2();
23            //TestClass1在这可以正常使用
24            Test1Class1 tmpObj3 = new Test1Class1();
25        }

26    }

27    namespace Test2
28    {
29        using Test1Class2 = com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
30 
31        class Program
32        {
33            static void Main(string[] args)
34            {
35                //Lib01Class1和Lib02Class2在这可以正常使用
36                Lib01Class1 tmpObj1 = new Lib01Class1();
37                Lib02Class2 tmpObj2 = new Lib02Class2();
38 
39                //注意这里,TestClass1在这不可以正常使用。
40                //因为,在Test2命名空间内不能使用Test1命名空间定义的别名
41                //Test1Class1 tmpObj3 = new Test1Class1();
42                
43                //TestClass2在这可以正常使用
44                Test1Class2 tmpObj3 = new Test1Class2();
45 
46                Console.WriteLine(tmpObj1);
47                Console.WriteLine(tmpObj2);
48                Console.WriteLine(tmpObj3);
49 
50                Console.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 Class1

20.如何手工释放资源?

答:

 .NET 平台在内存管理方面提供了GC(Garbage Collection),负责自动释放托管资源和内存回收的工作。但在以下两种情况需要我们手工进行资源释放:一、由于它无法对非托管资源进行释放,所以我们必须自己提供方法来释放对象内分配的非托管资源,比如你在对象的实现代码中使用了一个COM对象;二、你的类在运行是会产生大量实例(象 GIS 中的Geometry),必须自己手工释放这些资源以提高程序的运行效率

最理想的办法是通过实现一个接口显式的提供给客户调用端手工释放对象,System 命名空间内有一个 IDisposable 接口,拿来做这事非常合适,省得我们自己再声明一个接口了

示例:

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4 
 5namespace Example20
 6{
 7    class Program
 8    {
 9        class Class1 : IDisposable
10        {
11            //析构函数,编译后变成 protected void Finalize(),GC会在回收对象前会调用调用该方法
12            ~Class1()
13            {
14                Dispose(false);
15            }

16 
17            //通过实现该接口,客户可以显式地释放对象,而不需要等待GC来释放资源,据说那样会降低效率
18            void IDisposable.Dispose()
19            {
20                Dispose(true);
21            }

22 
23            //将释放非托管资源设计成一个虚函数,提供在继承类中释放基类的资源的能力
24            protected virtual void ReleaseUnmanageResources()
25            {
26                //Do something...
27            }

28 
29            //私有函数用以释放非托管资源
30            private void Dispose(bool disposing)
31            {
32                ReleaseUnmanageResources();
33 
34                //为true时表示是客户显式调用了释放函数,需通知GC不要再调用对象的Finalize方法
35                //为false时肯定是GC调用了对象的Finalize方法,所以没有必要再告诉GC你不要调用我的Finalize方法啦
36                if (disposing)
37                {
38                    GC.SuppressFinalize(this);
39                }

40            }
 
41        }

42        static void Main(string[] args)
43        {
44            //tmpObj1没有手工释放资源,就等着GC来慢慢的释放它吧
45            Class1 tmpObj1 = new Class1();
46 
47            //tmpObj2调用了Dispose方法,传说比等着GC来释放它效率要调一些
48            //个人认为是因为要逐个对象的查看其元数据,以确认是否实现了Dispose方法吧
49            //当然最重要的是我们可以自己确定释放的时间以节省内存,优化程序运行效率
50            Class1 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{
 7    class Program
 8    {
 9        static void Main(string[] args)
10        {
11            const int cycle = 10000;
12 
13            long vTickCount = Environment.TickCount;
14            String str = null;
15            for (int i = 0; i < cycle; i++)
16                str += i.ToString();
17            Console.WriteLine("String: {0} MSEL", Environment.TickCount - vTickCount);
18 
19            vTickCount = Environment.TickCount;
20            //看到这个变量名我就生气,奇怪为什么大家都使它呢? :)
21            StringBuilder sb = new StringBuilder();
22            for (int i = 0; i < cycle; i++)
23                sb.Append(i);
24            Console.WriteLine("StringBuilder: {0} MSEL", Environment.TickCount - vTickCount);
25 
26            string tmpStr1 = "A";
27            string tmpStr2 = tmpStr1;
28            Console.WriteLine(tmpStr1);
29            Console.WriteLine(tmpStr2);
30            //注意后面的输出结果,tmpStr1的值改变并未影响到tmpStr2的值
31            tmpStr1 = "B";
32            Console.WriteLine(tmpStr1);
33            Console.WriteLine(tmpStr2);
34 
35            Console.ReadLine();
36        }

37    }

38}

结果:
String: 375 MSEL
StringBuilder: 16 MSEL
A
A
B
A

 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值