abstract
修饰类名为抽象类,如果一个类为抽象类,则这个类只能是其他某个类的基类,换句话说这个类不可以被实例化,只能用其子类实例化,当一个非抽象类继承该基类时,需要实现该基类中所有的抽象方法,抽象类继承则不必须。同时抽象类中可以存在其他非抽象方法。 修饰方法为抽象方法,抽象方法是隐式的虚方法,只能声明在抽象类中,且没有函数体。也不能使用static或者virtual修饰符。当然也不能用private修饰,因为私有的抽象方法不能被重写,毫无存在意义。
代码案例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
abstract class _Abstract
{
void _A() { }//非抽象方法
protected abstract void _AA();
}
abstract class _AbstractSon1:_Abstract
{//抽象类继承抽象类不需要做任何事,当然也可以
//实现父类的抽象方法
protected override void _AA()//此方法可写也可不写
{
}
}
class _AbstractSon2 :_AbstractSon1
{
//若前面类实现了抽象方法,则其子类可不必须再次重写抽象方法
//当然也可以重写
protected override void _AA()//该方法不必须存在
{
}
}
class _AbstractSon3:_Abstract
{
protected override void _AA()//该方法必须村子
{
throw new NotImplementedException();
}
}
}
virtual
virtual 关键字用于修饰方法、属性、索引器或事件声明,并使它们可以在派生类中被重写。默认情况下,方法是非虚拟的。不能重写非虚方法。virtual 修饰符不能与 static、abstract, private 或 override 修饰符一起使用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
class AProgram
{
public virtual void AFun()//方法
{
Console.WriteLine("父类的虚方法");
}
public virtual event Action<int> a;//定义带一个参数的事件
public virtual string Current//属性
{
get;set;
}
int this[int index]//索引器
{
get
{
return 1;
}
}
}
class Program : AProgram
{
public override void AFun()//c重写
{
Console.WriteLine("子类的虚方法");
}
public override event Action<int> a;//重写
}
class ProgramSon:Program
{
static void Main(string[] args)
{
AProgram ap = new AProgram();
AProgram p = new Program();
AProgram pr = new Program();
AProgram ps = new ProgramSon();
ap.AFun();
p.AFun();
pr.AFun();
ps.AFun();
}
}
}
运行结果

结果分析
在Main方法中创建了四个AProgram对象,用这四个对象调用相同的AFun方法却不相同。下面来分析一下运行流程。
- ap.AFun():先检查类AProgram中的AFun方法---检查到是虚拟方法----转去检查实例类ap,就为本身--执行实例类ap中的方法----输出结果:父类的虚方法
- p.AFun()和pr.AFun():先检查类AProgram中的AFun方法---检查到是虚拟方法---转去检查实例类Program中是否有重写的----执行实例类Program中的方法----输出结果: 子类的虚方法
- ps.AFun():先检查类AProgram中的AFun方法---检查到是虚拟方法---转去检查实例类ProgramSon是否有重写的----没有重写的,去检查其父类Program中是否有重写的---执行实例类Program中的方法----输出结果: 子类的虚方法
分析来源
虚拟函数从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的,也就是写死了的!而虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运行时期对象实例来动态判断要调用的函数,其中那个申明时定义的类叫申明类,那个执行时实例化的类叫实例类。 如:飞禽 bird = new 麻雀(); 那么飞禽就是申明类,麻雀是实例类。 具体的检查的流程如下
- 当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚函数;
- 如果不是虚函数,那么它就直接执行该函数。而如果有virtual关键字,也就是一个虚函数,那么这个时候它就不会立刻执行该函数了,而是转去检查对象的实例类。
- 在这个实例类里,他会检查这个实例类的定义中是否有重新实现该虚函数(通过override关键字),如果是有,那么OK,它就不会再找了,而马上执行该实例类中的这个重新实现的函数。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了该虚函数的父类为止,然后执行该父类里重载后的函数。
在上面的规则中,可以看到,如果子类没有override的修饰,那么就算父类是virtual的方法,子类的方法也无法被调用,而会去它的父类中找override的方法,直到找到祖先类。所以在面向对象的开发过程中,如果要实现Dependency Injection、IoC等设计模式,就必须非常留意类设计中继承方法的声明,否则很可能导致实际的程序运行与预期不符。
override
override 方法提供从基类继承的成员的新实现。
- 由 override 声明重写的方法称为重写方法。重写的方法必须与 override 方法具有相同的签名。
- 重写的基方法必须是vitural,override,abstract类型的 ,不能重写非虚方法或是静态方法。所以override不能与vitural,new,static同时使用。
- override 属性,声明必须指定与继承属性完全相同的访问修饰符、类型和名称,并且被重写的属性必须是 virtual、abstract 或 override 的。(注意:这里是属性,不是字段,字段是不能被重写的)。
代码实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
abstract class _Override
{
public abstract int i;//错误,i是字段
public abstract int Current//抽象属性
{
get;
}
public abstract void oi();//方法
public virtual int Next//抽象字段
{
get;
set;
}
public abstract event Action<int> e;//抽象事件
public abstract int this[int index]//抽象索引器
{
get;
}
}
class _OverrideSon:_Override
{
public override int Current {
get => throw new NotImplementedException();
}
public override event Action<int> e;
public override void oi()
{
throw new NotImplementedException();
}
public override int Next {
get => base.Next;
set => base.Next = value;
}
public override int this[int index] =>
throw new NotImplementedException();
}
}
new
- 创建一个对象,过于简单,这里不再叙述。
- 修饰方法,由于隐藏基类方法。
- 用在泛型中,表示该泛型参数T必须存在默认构造器,及无参数构造器。
代码实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
class _New
{
public void father()
{
Console.WriteLine("我是父亲");
}
}
class _NewSon:_New
{
public new void father()
{
Console.WriteLine("我是儿子");
}
}
class _NewSon2:_New
{
static void Main()
{
_New s = new _NewSon2();
_New s1 = new _NewSon();
_New s2 = new _New();
_NewSon s4 = new _NewSon();
s.father();
s1.father();
s2.father();
s4.father();
_NewSon3<_NewSon2> n = new _NewSon3<_NewSon2>(2);//初始化成功,_NewSon3具有无参构造器
_NewSon3<_NewSon4> n2 = new _NewSon3<_NewSon4>(2);//初始化失败,_NewSon4不具有无参构造器
}
}
class _NewSon3<T> where T:new()//检查参数T类是否具有无参构造器
{
public _NewSon3(int T)
{
}
}
class _NewSon4
{
public _NewSon4(int i)
{
}
}
}
结果分析
- s.father(); s1.father();s2.father();输出结果都为:我是父亲。多态下调用父类智能调用其自身所定义的方法和在其子类中重写的方法
- n初始化成功,n2失败,因为_NewSon4不具有无参构造器。
sealed
- 当对一个类应用sealed修饰符时,此修饰符会阻止其他类从该类继承。
- sealed方法必须与override连用,密封方法重写基类中的方法,但其派生出的子类不能在进一步重写该方法。
- sealed中不能定义新的虚成员(包括方法、属性、索引器、事件)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
class _Sealed
{
public virtual void _A()
{
}
}
sealed class _SealedSon:_Sealed
{
public sealed override void _A()
{
base._A();
}
}
class _SealedSon1:_SealedSon//错误,不可以继承密封类
{
}
class _SealedSon2 : _Sealed//重写_A方法
{
public override void _A()
{
base._A();
}
}
class _SealedSon3:_Sealed
{
public sealed override void _A()//重写时使用sealed关键字
{
base._A();
}
}
class _SealedSon4:_SealedSon3
{
public sealed override void _A()//因为_SealedSon3对_A重写时使用了sealed
//所以其_SealedSon4无法再次对_A重写
{
base._A();
}
}
class _SealedSon5:_SealedSon2
{
public sealed override void _A()//普通就可以重写
{
base._A();
}
}
}
typeof
用来获取一个类的类型,和GetType()方法的功能类似,但二者又有一些不同点:
- Typeof不是运算符而是方法
- GetType()是基类System.Object的方法,因此只有建立一个实例之后才能够被调用(初始化以后)
- Typeof的参数只能是int,string,String,自定义类型,且不能是实例
- GetType()和typeof都返回System.Type的引用.
代码实例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
class _Typeof
{
static void Main()
{
int i = 0;
Type type = typeof(int);//System.Int32;
// Type type1 = typeof(i);//错误
Type type2 = i.GetType();//
Console.WriteLine(type + " " + type2);
}
}
class _TypeofSon
{
}
}
params
params表示参数是可变个数的。在不加params修饰时,只能往里传入一个数组。调用方式1。在加了params后,不仅可以传入数组,还可以传入多个参数,这多个参数等价为数组。如调用方式2。 注意事项:
- params只能用在一维数组上,且必须是最后一个参数。
- 不能使用params来重载方法。因为重载是通过参数个数不同或者相同位置参数类型不同来区分的,这两种函数,函数的参数个数和参数类型都相同,所以不能区分。
- 不允许ref和out 与 params同时使用,加上之后程序编译时报错。因为数组的传递的本来就是引用(只限于用数组的方式进行调用)。
- 一个没有params的方法的优先级高于带有params的方法
代码实例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
class _Params
{
public static void add(params int[] i)
{
Console.WriteLine("可变参数加法");
}
public static void add(int i,int j)
{
Console.WriteLine("不可变参数加法");
}
public static void mix(int i,params int[] j)
{//j必须放在i后边
}
static void Main()
{
_Params.add(new int[] { });//方式一
_Params.add(2, 3);
_Params.add(1, 3, 4, 35, 35, 56);//方式二
}
}
}
运行结果

is
用于强制类型转换,检查一个对象是否兼容于其指定的类型,并返回一个Bool值,永远不会抛出异常。一般使用方式如下。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
class _Is
{
}
class _IsSon:_Is
{
static void Main()
{
_IsSon i = new _IsSon();
if(i is _Is)
{
_Is i1 = (_Is)i;
Console.WriteLine("转换成功");
}
}
}
}
在上面的代码中,CLR实际上会检查两次对象的类型,is操作符先核实一次,如果i兼容于_Is,那么在(_Is)i时会再次核实一次,效率比较低,同时说明了is只是单纯的检查是否能够实现类型兼容,而不能转换。若要既检查又要实现转换可以用as。
as
用于强制类型转换,既会检查又可以实现转换,而且永远不会抛出异常,即如果转换不成功,会返回null。用法如下。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.KeyWord
{
class _As
{
}
class _AsSon:_As
{
static void Main()
{
_As i = new _AsSon();
_AsSon a = i as _AsSon;
if(a!=null)
{
Console.WriteLine("转换成功");
}
}
}
}
在上面的代码中,CLR只需要检查一次是否兼容,而且检查完毕后会执行相应的转换,比is效率高。