现在的位置: 首页 > 综合 > 正文

c#基础概念二十五问[转]

2012年12月16日 ⁄ 综合 ⁄ 共 22331字 ⁄ 字号 评论关闭
当初学 C# 时是找个人大概问了一下数据类型和分支语句就开始做项目了。这两天又全面的看了一下相关的基础知识(学而时习之嘛),总结了25个问题:

1.静态成员和非静态成员的区别?
2.const 和 static readonly 区别?
3.extern 是什么意思?
4.abstract 是什么意思?
5.internal 修饰符起什么作用?
6.sealed 修饰符是干什么的?
7.override 和 overload 的区别?
8.什么是索引指示器?
9.new 修饰符是起什么作用?
10.this 关键字的含义?
11.可以使用抽象函数重写基类中的虚函数吗?
12.密封类可以有虚函数吗?
13.什么是属性访问器?
14.abstract 可以和 virtual 一起使用吗?可以和 override 一起使用吗?
15.接口可以包含哪些成员?
16.类和结构的区别?
17.接口的多继承会带来哪些问题?
18.抽象类和接口的区别?
19.别名指示符是什么?
20.如何手工释放资源?
21.P/Invoke是什么?
22.StringBuilder 和 String 的区别?
23.explicit 和 implicit 的含义?
24.params 有什么用?
25.什么是反射?

以下是我做的一份参考答案(C# 语言范畴之内),如果有不准确、不全面的,欢迎各位朋友指正!

 
1.静态成员和非静态成员的区别?

答:

静态变量使用 static 修饰符进行声明,在类被实例化时创建,通过类进行访问

不带有 static 修饰符声明的变量称做非静态变量,在对象被实例化时创建,通过对象进行访问

一个类的所有实例的同一静态变量都是同一个值,同一个类的不同实例的同一非静态变量可以是不同的值

静态函数的实现里不能使用非静态成员,如非静态变量、非静态函数等

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example01
{
    class Program
    {
        class Class1
        {
            public static String staticStr = "Class";
            public String notstaticStr = "Obj";
        }
        static void Main(string[] args)
        {
            //静态变量通过类进行访问,该类所有实例的同一静态变量都是同一个值
            Console.WriteLine("Class1's staticStr: {0}", Class1.staticStr);
 
            Class1 tmpObj1 = new Class1();
            tmpObj1.notstaticStr = "tmpObj1";
            Class1 tmpObj2 = new Class1();
            tmpObj2.notstaticStr = "tmpObj2";
 
            //非静态变量通过对象进行访问,不同对象的同一非静态变量可以有不同的值
            Console.WriteLine("tmpObj1's notstaticStr: {0}", tmpObj1.notstaticStr);
            Console.WriteLine("tmpObj2's notstaticStr: {0}", tmpObj2.notstaticStr);
 
            Console.ReadLine();
        }
    }
}

结果:
Class1's staticStr: Class
tmpObj1's notstaticStr: tmpObj1
tmpObj2's notstaticStr: tmpObj2

2.const 和 static readonly 区别?

答:

const

用 const 修饰符声明的成员叫常量,是在编译期初始化并嵌入到客户端程序

static readonly

用 static readonly 修饰符声明的成员依然是变量,只不过具有和常量类似的使用方法:通过类进行访问、初始化后不可以修改。但与常量不同的是这种变量是在运行期初始化

示例:

测试类:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example02Lib
{
    public class Class1
    {
        public const String strConst = "Const";
        public static readonly String strStaticReadonly = "StaticReadonly";
        //public const String strConst = "Const Changed";
        //public static readonly String strStaticReadonly = "StaticReadonly Changed";
    }
}
 
客户端代码:
using System;
using System.Collections.Generic;
using System.Text;
using Example02Lib;
 
namespace Example02
{
    class Program
    {
        static void Main(string[] args)
        {
            //修改Example02中Class1的strConst初始值后,只编译Example02Lib项目
            //然后到资源管理器里把新编译的Example02Lib.dll拷贝Example02.exe所在的目录,执行Example02.exe
            //切不可在IDE里直接调试运行因为这会重新编译整个解决方案!!
 
            //可以看到strConst的输出没有改变,而strStaticReadonly的输出已经改变
            //表明Const变量是在编译期初始化并嵌入到客户端程序,而StaticReadonly是在运行时初始化的
            Console.WriteLine("strConst : {0}", Class1.strConst);
            Console.WriteLine("strStaticReadonly : {0}", Class1.strStaticReadonly);
 
            Console.ReadLine();
        }
    }
}

结果:
strConst : Const
strStaticReadonly : StaticReadonly

修改后的示例:

测试类:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example02Lib
{
    public class Class1
    {
        //public const String strConst = "Const";
        //public static readonly String strStaticReadonly = "StaticReadonly";
        public const String strConst = "Const Changed";
        public static readonly String strStaticReadonly = "StaticReadonly Changed";
    }
}

结果

strConst : Const
strStaticReadonly : StaticReadonly Changed

3.extern 是什么意思?

答:

extern 修饰符用于声明由程序集外部实现的成员函数

经常用于系统API函数的调用(通过 DllImport )。注意,和DllImport一起使用时要加上 static 修饰符

也可以用于对于同一程序集不同版本组件的调用(用 extern 声明别名)

不能与 abstract 修饰符同时使用

示例:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
 
namespace Example03
{
    class Program
    {
        //注意DllImport是一个Attribute Property,在System.Runtime.InteropServices命名空间中定义
        //extern与DllImport一起使用时必须再加上一个static修饰符
        [DllImport("User32.dll")]
        public static extern int MessageBox(int Handle, string Message, string Caption, int Type);
 
        static int Main()
        {
            string myString;
            Console.Write("Enter your message: ");
            myString = Console.ReadLine();
            return MessageBox(0, myString, "My Message Box", 0);
        }
    }
}

结果:

4.abstract 是什么意思?

答:

abstract 修饰符可以用于类、方法、属性、事件和索引指示器(indexer),表示其为抽象成员

abstract 不可以和 static 、virtual 一起使用

声明为 abstract 成员可以不包括实现代码,但只要类中还有未实现的抽象成员(即抽象类),那么它的对象就不能被实例化,通常用于强制继承类必须实现某一成员

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example04
{
    #region 基类,抽象类
    public abstract class BaseClass
    {
        //抽象属性,同时具有get和set访问器表示继承类必须将该属性实现为可读写
        public abstract String Attribute
        {
            get;
            set;
        }
 
        //抽象方法,传入一个字符串参数无返回值
        public abstract void Function(String value);
 
        //抽象事件,类型为系统预定义的代理(delegate):EventHandler
        public abstract event EventHandler Event;
 
        //抽象索引指示器,只具有get访问器表示继承类必须将该索引指示器实现为只读
        public abstract Char this[int Index]
        {
            get;
        }
    }
    #endregion
 
    #region 继承类
    public class DeriveClass : BaseClass
    {
        private String attribute;
 
        public override String Attribute
        {
            get
            {
                return attribute;
            }
            set
            {
                attribute = value;
            }
        }
        public override void Function(String value)
        {
            attribute = value;
            if (Event != null)
            {
                Event(this, new EventArgs());
            }
        }
        public override event EventHandler Event;
        public override Char this[int Index]
        {
            get
            {
                return attribute[Index];
            }
        }
    }
    #endregion
 
    class Program
    {
        static void OnFunction(object sender, EventArgs e)
        {
            for (int i = 0; i < ((DeriveClass)sender).Attribute.Length; i++)
            {
                Console.WriteLine(((DeriveClass)sender)[i]);
            }
        }
        static void Main(string[] args)
        {
            DeriveClass tmpObj = new DeriveClass();
 
            tmpObj.Attribute = "1234567";
            Console.WriteLine(tmpObj.Attribute);
 
            //将静态函数OnFunction与tmpObj对象的Event事件进行关联
            tmpObj.Event += new EventHandler(OnFunction);
 
            tmpObj.Function("7654321");
 
            Console.ReadLine();
        }
    }
}

结果:
1234567
7
6
5
4
3
2
1

5.internal 修饰符起什么作用?

答:

internal 修饰符可以用于类型或成员,使用该修饰符声明的类型或成员只能在同一程集内访问

接口的成员不能使用 internal 修饰符

示例

Example05Lib 项目的 Class1

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example05Lib
{
    public class Class1
    {
        internal String strInternal = null;
        public String strPublic;
    }
}

结果
Example05Lib 项目的 Class2 类可以访问到 Class1 的 strInternal 成员

Example05 项目的 Program 类无法访问到 Class1 的 strInternal 成员

6.sealed 修饰符是干什么的?

答:

sealed 修饰符表示密封

用于类时,表示该类不能再被继承,不能和 abstract 同时使用,因为这两个修饰符在含义上互相排斥

用于方法和属性时,表示该方法或属性不能再被继承,必须和 override 关键字一起使用,因为使用 sealed 修饰符的方法或属性肯定是基类中相应的虚成员

通常用于实现第三方类库时不想被客户端继承,或用于没有必要再继承的类以防止滥用继承造成层次结构体系混乱

恰当的利用 sealed 修饰符也可以提高一定的运行效率,因为不用考虑继承类会重写该成员

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example06
{
    class Program
    {
        class A
        {
            public virtual void F()
            {
                Console.WriteLine("A.F");
            }
            public virtual void G()
            {
                Console.WriteLine("A.G");
            }
        }
        class B : A
        {
            public sealed override void F()
            {
                Console.WriteLine("B.F");
            }
            public override void G()
            {
                Console.WriteLine("B.G");
            }
        }
        class C : B
        {
            public override void G()
            {
                Console.WriteLine("C.G");
            }
        }
        static void Main(string[] args)
        {
            new A().F();
            new A().G();
            new B().F();
            new B().G();
            new C().F();
            new C().G();
 
            Console.ReadLine();
        }
    }
}

结果:
类 B 在继承类 A 时可以重写两个虚函数,如图所示:

由于类 B 中对 F 方法进行了密封, 类 C 在继承类 B 时只能重写一个函数,如图所示:

控制台输出结果,类 C 的方法 F 只能是输出 类B 中对该方法的实现:

A.F
A.G
B.F
B.G
B.F
C.G

7.override 和 overload 的区别?

答:

override 表示重写,用于继承类对基类中虚成员的实现

overload 表示重载,用于同一个类中同名方法不同参数(包括类型不同或个数不同)的实现

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example07
{
    class Program
    {
        class BaseClass
        {
            public virtual void F()
            {
                Console.WriteLine("BaseClass.F");
            }
        }
        class DeriveClass : BaseClass
        {
            public override void F()
            {
                base.F();
                Console.WriteLine("DeriveClass.F");
            }
            public void Add(int Left, int Right)
            {
                Console.WriteLine("Add for Int: {0}", Left + Right);
            }
            public void Add(double Left, double Right)
            {
                Console.WriteLine("Add for int: {0}", Left + Right);
            }
        }
        static void Main(string[] args)
        {
            DeriveClass tmpObj = new DeriveClass();
            tmpObj.F();
            tmpObj.Add(1, 2);
            tmpObj.Add(1.1, 2.2);
 
            Console.ReadLine();
        }
    }
}

结果:
BaseClass.F
DeriveClass.F
Add for Int: 3
Add for int: 3.3

8.什么是索引指示器?

答:

实现索引指示器(indexer)的类可以象数组那样使用其实例后的对象,但与数组不同的是索引指示器的参数类型不仅限于int

简单来说,其本质就是一个含参数属性

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example08
{
    public class Point
    {
        private double x, y;
        public Point(double X, double Y)
        {
            x = X;
            y = Y;
        }
        //重写ToString方法方便输出
        public override string ToString()
        {
            return String.Format("X: {0} , Y: {1}", x, y);
        }
    }
    public class Points
    {
        Point[] points;
        public Points(Point[] Points)
        {
            points = Points;
        }
        public int PointNumber
        {
            get 
            { 
                return points.Length; 
            }
        }    
        //实现索引访问器
        public Point this[int Index]
        {
            get
            {
                return points[Index];
            }
        }
    }
 
    //感谢watson hua(http://huazhihao.cnblogs.com/)的指点
    //索引指示器的实质是含参属性,参数并不只限于int
    class WeatherOfWeek
    {
        public string this[int Index]
        {
            get
            {
                //注意case段使用return直接返回所以不需要break
                switch (Index)
                {
                    case 0:
                        {
                            return "Today is cloudy!";
                        }
                    case 5:
                        {
                            return "Today is thundershower!";
                        }
                    default:
                        {
                            return "Today is fine!";
                        }
                }
            }
        }
        public string this[string Day]
        {
            get
            {
                string TodayWeather = null;
                //switch的标准写法
                switch (Day)
                {
                    case "Sunday":
                        {
                            TodayWeather = "Today is cloudy!";
                            break;
                        }
                    case "Friday":
                        {
                            TodayWeather = "Today is thundershower!";
                            break;
                        }
                    default:
                        {
                            TodayWeather = "Today is fine!";
                            break;
                        }
                }
                return TodayWeather;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Point[] tmpPoints = new Point[10];
            for (int i = 0; i < tmpPoints.Length; i++)
            {
                tmpPoints[i] = new Point(i, Math.Sin(i));
            }
 
            Points tmpObj = new Points(tmpPoints);
            for (int i = 0; i < tmpObj.PointNumber; i++)
            {
                Console.WriteLine(tmpObj[i]);
            }
 
 
            string[] Week = new string[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Staurday"};
            WeatherOfWeek tmpWeatherOfWeek = new WeatherOfWeek();
            for (int i = 0; i < 6; i++)
            {
                Console.WriteLine(tmpWeatherOfWeek[i]);
            }
            foreach (string tmpDay in Week)
            {
                Console.WriteLine(tmpWeatherOfWeek[tmpDay]);
            }
 
            Console.ReadLine();
        }
    }
}

结果:
X: 0 , Y: 0
X: 1 , Y: 0.841470984807897
X: 2 , Y: 0.909297426825682
X: 3 , Y: 0.141120008059867
X: 4 , Y: -0.756802495307928
X: 5 , Y: -0.958924274663138
X: 6 , Y: -0.279415498198926
X: 7 , Y: 0.656986598718789
X: 8 , Y: 0.989358246623382
X: 9 , Y: 0.412118485241757
Today is cloudy!
Today is fine!
Today is fine!
Today is fine!
Today is fine!
Today is thundershower!
Today is cloudy!
Today is fine!
Today is fine!
Today is fine!
Today is fine!
Today is thundershower!
Today is fine!

9.new 修饰符是起什么作用?

答:

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

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

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

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

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example09
{
    class BaseClass
    {
        //基类设计者声明了一个PI的公共变量,方便进行运算
        public static double PI = 3.1415;
    }
    class DervieClass : BaseClass
    {
        //继承类发现该变量的值不能满足运算精度,于是可以通过new修饰符显示隐藏基类中的声明
        public new static double PI = 3.1415926;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(BaseClass.PI);
            Console.WriteLine(DervieClass.PI);
 
            Console.ReadLine();
        }
    }
}

结果:
3.1415
3.1415926

10.this 关键字的含义?

答:

this 是一个保留字,仅限于构造函数和方法成员中使用

在类的构造函数中出现表示对正在构造的对象本身的引用,在类的方法中出现表示对调用该方法的对象的引用,在结构的构造上函数中出现表示对正在构造的结构的引用,在结构的方法中出现表示对调用该方法的结果的引用

this 保留字不能用于静态成员的实现里,因为这时对象或结构并未实例化

在 C# 系统中,this 实际上是一个常量,所以不能使用 this++ 这样的运算

this 保留字一般用于限定同名的隐藏成员、将对象本身做为参数、声明索引访问器、判断传入参数的对象是否为本身

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example10
{
    class Class1
    {
        private double c;
        private string value;
 
        public double C
        {
            get
            {
                return c;
            }
        }
        public Class1(double c)
        {
            //限定同名的隐藏成员
            this.c = c;
        }
        public Class1(Class1 value)
        {
            //用对象本身实例化自己没有意义
            if (this != value)
            {
                c = value.C;
            }
        }
        public override string ToString()
        {
            //将对象本身做为参数
            return string.Format("{0} Celsius = {1} Fahrenheit", c, UnitTransClass.C2F(this));
        }
 
        //由于好奇,在这做了一个效率测试,想看看到底哪种方式访问成员变量更快,结论:区别不大。。。
        public string Test1()
        {
            long vTickCount = Environment.TickCount;
            for (int i = 0; i < 10000000; i++)
                this.value = i.ToString();
            return string.Format("Have this.: {0} MSEL", Environment.TickCount - vTickCount);
        }
        public string Test2()
        {
            long vTickCount = Environment.TickCount;
            for (int i = 0; i < 10000000; i++)
                value = i.ToString();
            return string.Format("Don't have this.: {0} MSEL", Environment.TickCount - vTickCount);
        }
    }
    class UnitTransClass
    {
        public static double C2F(Class1 value)
        {
            //摄氏到华氏的转换公式
            return 1.8 * value.C + 32;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Class1 tmpObj = new Class1(37.5);
 
            Console.WriteLine(tmpObj);
 
            Console.WriteLine(tmpObj.Test1());
            Console.WriteLine(tmpObj.Test2());
 
            Console.ReadLine();
        }
    }
}

结果:
37.5 Celsius = 99.5 Fahrenheit
Have this.: 4375 MSEL
Don't have this.: 4406 MSEL

11.可以使用抽象函数重写基类中的虚函数吗?

答:

可以

需使用 new 修饰符显式声明,表示隐藏了基类中该函数的实现

或增加 override 修饰符,表示抽象重写了基类中该函数的实现

示例:

    class BaseClass
    {
        public virtual void F()
        {
            Console.WriteLine("BaseClass.F");
        }
    }
    abstract class  DeriveClass1 : BaseClass
    {
        public abstract new void F();
    }
 
    //感谢watson hua(http://huazhihao.cnblogs.com/)的指点
    //是他提醒了我还可以用这种方法抽象重写基类的虚方法
    abstract class DeriveClass2 : BaseClass
    {
        public abstract override void F();
    }

12.密封类可以有虚函数吗?

答:

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

示例:

    class BaseClass
    {
        public virtual void F()
        {
            Console.WriteLine("BaseClass.F");
        }
    }
    sealed class DeriveClass : BaseClass
    {
        //基类中的虚函数F被隐式的转化为非虚函数
 
        //密封类中不能再声明新的虚函数G
        //public virtual void G()
        //{
        //    Console.WriteLine("DeriveClass.G");
        //}
    }

13.什么是属性访问器?

答:

属性访问器(Property Accessor),包括 get 访问器和 set 访问器分别用于字段的读写操作

其设计目的主要是为了实现面向对象(OO)中的封装思想。根据该思想,字段最好设为private,一个精巧的类最好不要直接把字段设为公有提供给客户调用端直接访问

另外要注意属性本身并不一定和字段相联系

14.abstract 可以和 virtual 一起使用吗?可以和 override 一起使用吗?

答:

abstract 修饰符不可以和 static、virtual 修饰符一起使用

abstract 修饰符可以和 override 一起使用,参见第11点

示例:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example14
{
    class BaseClass
    {
        public virtual void F()
        {
            Console.WriteLine("BaseClass.F");
        }
    }
    abstract class DeriveClass1 : BaseClass
    {
        //在这里, abstract是可以和override一起使用的
        public abstract override void F();
    }
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

15.接口可以包含哪些成员?

答:

接口可以包含属性、方法、索引指示器和事件,但不能包含常量、域、操作符、构造函数和析构函数,而且也不能包含任何静态成员

 

16.类和结构的区别?

答:
类:

类是引用类型在堆上分配,类的实例进行赋值只是复制了引用,都指向同一段实际对象分配的内存

类有构造和析构函数

类可以继承和被继承

结构:

结构是值类型在栈上分配(虽然栈的访问速度比较堆要快,但栈的资源有限放),结构的赋值将分配产生一个新的对象。

结构没有构造函数,但可以添加。结构没有析构函数

结构不可以继承自另一个结构或被继承,但和类一样可以继承自接口

 

示例:

根据以上比较,我们可以得出一些轻量级的对象最好使用结构,但数据量大或有复杂处理逻辑对象最好使用类。

如:Geoemtry(GIS 里的一个概论,在 OGC 标准里有定义) 最好使用类,而 Geometry 中点的成员最好使用结构

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Example16
{
    interface IPoint
    {
        double X
        {
            get;
            set;
        }
        double

【上篇】
【下篇】

抱歉!评论已关闭.