ü ref、out、与params
应该来说这三个关键在一般的编码过程中还是会不时涉及到的,所以不算什么“冷僻”的概念。有关三个参数修饰符的解释如下:
Ref:ref关键字让一个值类型的输入参数按引用传递。实际上,对于引用类型的参数,是否使用ref关键字,差别微乎其微。有一个例外是String类型的参数,由于C#中的String对象是不可修改的,所以要想真正达到参数引用传递的效果,需要使用ref关键字。
Out:使用out关键字表示此函数参数为输出参数。Out关键字主要应该是用于实现函数的多个返回值,使用它修饰参数的唯一好处在于变量在传递给out类型参数前,不需要初始化。
Params:params关键字指明若干输入参数将被看作一个参数数组。Params关键字只能用于参数列表的最后一个参数,这也很好理解。实际上我个人认为三个参数修饰符中,params是比较有意义同时用处较大的。
ü const与readonly
大多数人会注意到const、readonly的一个区别,使用const修饰的字段是“编译时常量”,使用readonly修饰的字段是“运行时常量”(实际上来说它并非真正意义上的常量),借助以下例子可以帮助理解:
Public const int ConstSample=1;
Public readonly DateTime RoSample=DateTime.Now;
除此之外,const、readonly之间还有其它一些差别,包括:
*常量字段总是静态的(它不用并且不能显式声明为static),只读字段可以声明为静态的,但默认不是;
*常量字段必须在声明时初始化。只读字段可以在声明时初始化,也可以在构造函数中初始化,或者不初始化;
*常量字段只能是内建的值类型、字符串以及枚举值,只读字段没有这一项限制。
ü 静态构造函数
通常是一些外企在笔试时会问到关于静态构造函数的问题,老外的想法就是不一样。平心而论用到静态构造函数的地方不多,至少对我而言静态构造函数没派上什么用场。
对于笔试,有三点需要注意一下:
*静态构造函数不允许有访问修饰符;
*静态构造函数不允许被显式调用;
*静态构造函数在加载类时实调用一次,即在创建类的任何实例(在非静态构造函数前执行)和引用类的任何成量之前。
ü 多态
首先你最好能清楚说明多态的概念,也许你天天用,却不一定能表述明白。作为OO编程中的三大特性之一(另两个是封装、继承),可以说是OO编程的实质,它意味着可以有多个可互换使用的类,即使每个类以不同的方式实现相同的方法或属性。
C#包含两类的多态:
*编译时多态,通过方法的重载(overload)来实现,对同一方法名称,程序员只需给出不同的参数,编译时系统将确定具体调用哪个方法;
*运行时多态,通过方法的重写(override)来实现,当调用基类的虚方法(virtual)时,JIT负责确定实例的实际类型,例如下面的例子,JIT最终会确定实例的实际类型是普通人还是超人,并最终调用相应派生类中的方法:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
class Program
{
static void Main(string[] args)
{
Sample1.M1(new OrdinaryPeople("ZhangSan"));
Sample1.M1(new Superman("Hancock"));
Console.Read();
}
}
class Sample1
{
public static void M1(Person p)
{
p.Introdce();
}
}
class Person
{
protected string mName;
public Person(string pName)
{
this.mName = pName;
}
public virtual void Introdce()
{
}
}
class OrdinaryPeople : Person
{
public OrdinaryPeople(string pName)
: base(pName)
{
}
public override void Introdce()
{
Console.WriteLine("Hello,I am an ordinary person," + this.mName);
}
}
class Superman : Person
{
public Superman(string pName)
: base(pName)
{
}
public override void Introdce()
{
Console.WriteLine("Hi,I am a Superman," + this.mName);
}
}
对于多态,还必需提一个C#中的关键字:new。前面提到,对于virtual方法,JIT会确定实例的实际类型然后决定调用什么方法。但是如果派生类中new关键字修饰方法,则它向CLR澄清此派生类中的方法与基类中的方法毫无关系,以下代码最终调用是基类的introduce方法:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
class Program
{
static void Main(string[] args)
{
Sample1.M1(new OrdinaryPeople("ZhangSan"));
Sample1.M1(new Superman("Hancock"));
Console.Read();
}
}
class Sample1
{
public static void M1(Person p)
{
p.Introdce();
}
}
class Person
{
protected string mName;
public Person(string pName)
{
this.mName = pName;
}
public virtual void Introdce()
{
Console.WriteLine("Hello,I am " + this.mName);
}
}
class OrdinaryPeople : Person
{
public OrdinaryPeople(string pName)
: base(pName)
{
}
public new void Introdce()
{
Console.WriteLine("Hello,I am an ordinary person," + this.mName);
}
}
class Superman : Person
{
public Superman(string pName)
: base(pName)
{
}
public new void Introdce()
{
Console.WriteLine("Hi,I am a Superman," + this.mName);
}
}
ü 操作符重载
对于操作符重载我认为掌握基本的语法即可(况且它们不属于CLS)。以下是Grant Palmer的《C#程序员参考手册上的例子》,实际上演示了复数的加法运算,输出 5-3i:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
class Program
{
static void Main(string[] args)
{
Complex a = new Complex(3, 4);
Complex b = new Complex(2, -7);
Complex c = a + b;
Console.WriteLine(c);
Console.Read();
}
}
public class Complex
{
double a, b;
public Complex(double d1, double d2)
{
this.a = d1;
this.b = d2;
}
public static Complex operator +(Complex c1, Complex c2)
{
Complex c3 = new Complex(c1.a + c2.a, c1.b + c2.b);
return c3;
}
public override string ToString()
{
if (b < 0)
{
return a.ToString() + "-" + Math.Abs(b) + "i";
}
else
{
return a.ToString() + "+" + b + "i";
}
}
}
ü 不安全代码
不安全代码指(显式)使用了指针的代码。注意两个关键字:unsafe、fixed。Unsafe适用于类、结构体、方法、字段、类或结构体的某一代码块。
由于C#中引用类型的对象位于堆上,而堆是由垃圾回收器管理,为避免内存碎片,有时候垃圾回收器需要移动这些对象,如果你显式使用指针访问这些内容,这样就很容易造成无效指针,这时就需要使用fixed关键字,指明保持此对象在堆上的地址是固定的。以下示例中M1方法使用了fixed,因为数组为引用类型(如果不使用,将出现编译错误“只能获取 fixed 语句初始值设定项内的未固定表达式的地址”),而M2方法则无需使用fixed。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
class Program
{
static void Main(string[] args)
{
int[] ar = new int[5] { 1,2,3,4,5};
M1(ar);
M2(ar[0]);
Console.Read();
}
unsafe static void M1(int[] ar)
{
fixed (int* x = &ar[0])
{
for (int i = 0; i < ar.Length; i++)
{
Console.WriteLine(*(x + i));
}
}
}
unsafe static void M2(int x)
{
int* i = &x;
Console.WriteLine(*i);
}
}
ü 访问修饰符
1)对于普通的类来说,仅可以使用public或internal(类的默认访问修饰符)访问修饰符,嵌套类可以使用public、internal、protected、private、protected interanl。
2)成员字段的访问修饰符包括:public、internal、protected、protected internal、private(默认访问修饰符)。其中internal指该成员字段可以被相同程序集中的类访问,internal指该成员字段可以被其派生类访问。Protected internal指protected or internal。
ü 单例模式
单例模式是最简单的一种设计模式,不过在写法上还是有一些细节值得注意的,下面是两个例子:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public sealed class Singleton
{
private static Singleton mIns;
private static object mLock = new object();
private Singleton()
{
}
public static Singleton GegInstance()
{
lock (mLock)
{
if (mIns == null)
{
mIns = new Singleton();
}
return mIns;
}
}
}
//
public sealed class Singleton
{
public static readonly Singleton mIns = new Singleton();
}
ü 线程安全
关于线程安全的概念,以ATM机的取款程序来说,假设“用户存款”类中有一个“取款”的方法分为三步:(1)判断余额、(2)调用硬件接口出钞、(3)修改余额。显而易见,在系统对CPU时间片的调度中,中断可能发生在各步骤之间,那么当多线程操作时,很可能造成最终余额不正确。
对于.NET类库中的类与用户定义的类来说,绝大多数的操作与上述的“取款”一样,不是原子操作,因而很可能造成对象最终处于一个不确定的状态。MSDN中对于每一个类,都附带说明其线程安全的的情况(当然,大多数在多线程写的情况下都是不安全的,包括我们常用的集合类、UI控件类等等)。对于这些非线程安全的类来说,只能通过用户在编码的过程中通过同步的机制来确保代码的线程安全。