参考:
Microsoft文档:C#关键字
https://www.cnblogs.com/jiajiayuan/archive/2011/09/14/2176015.html
https://www.cnblogs.com/aehyok/p/3519599.html
https://www.cnblogs.com/adawoo/p/6434009.html
https://www.cnblogs.com/lycb/p/11273772.html
https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/sealed
https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/extern
base “基类”
public class A
{
public virtual void Say()
{
Debug.Log("Im A");
}
}
public class B : A
{
public Son()
: base() //指定:创建B类实例时应调用其基类的构造函数。
{}
public override void Say()
{
base.say(); //调用其基类上已被其他方法重写的方法。
Debug.Log("Im B");
}
}
this “自身”
this用于引用类的当前实例,在静态属性、静态方法或静态字段初始值中无效
public class A
{
public string Name { get; set; }
public Rename(string Name)
{
this.Name = Name; //1.this指当前类的实例,即“自身”,可以用来指定被因名称相同而被隐藏的成员变量
}
public void CallName(A aaa)
{
Console.WriteLine(aaa.Name);
}
public void Call()
{
CallName(this); //2.将当前实例作为参数传递到其他方法
}
string[] List = new string[10];
public string this[int param] //3.声明索引器
{
get { return List[param]; }
set { List[param] = value; }
}
}
virtual “虚”–可重写、覆盖
- 用关键字 virtual 修饰的方法,叫虚方法。
- 虚方法的实现可以由派生类所取代,即在子类中用override 声明同名的方法(重写)。 “重写”会改变父类方法的功能。
- 虚方法前不允许有static,abstract,或override修饰符。
- 虚方法不能是私有的,因此不能使用private修饰符。
虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运行时期对象实例来动态判断要调用的函数:
- 当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚函数;
- 如果不是虚函数,那么它就直接执行该函数。而如果是一个虚函数,那么这个时候它就不会立刻执行该函数了,而是开始检查对象的实例类。
- 在这个实例类里,他会检查这个实例类的定义中是否有实现该虚函数或者重新实现该虚函数(通过override关键字)的方法,如果有,它就不会再找了,而是马上执行该实例类中实现的虚函数的方法。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了该虚函数的父类为止,然后执行该父类里重载后的函数。
class A
{
public virtual void Sum()
{
Console.WriteLine("I am A Class,I am virtual sum().");
}
}
class B : A
{
public override void Sum() // 重新实现了虚函数
{
Console.WriteLine("I am B Class,I am override sum().");
}
}
class C : B
{
}
class Program
{
static void Main(string[] args)
{
A a=new C();// 定义一个a这个A类的对象.这个A就是a的申明类,实例化a对象,C是a的实例类
a.Sum();
Console.Read();
}
}
执行a.Sum:
- 先检查申明类A。
- 检查到是虚拟方法。
- 转去检查实例类C,无重写的方法。
- 转去检查类C的父类B,有重写的方法。
- 执行父类B中的Sum方法。
- 输出结果 I am B Class,I am override sum()。
new “覆盖”
用 new 关键字 修饰 定义的与父类中同名的方法,叫覆盖。 “覆盖”不会改变父类方法的功能。
class A
{
public virtual void Sum()
{
Console.WriteLine("I am A Class,I am virtual sum().");
}
}
class B : A
{
public new void Sum() //覆盖父类里的同名函数,而不是重新实现
{
Console.WriteLine("I am B Class,I am new sum().");
}
}
class Program
{
static void Main(string[] args)
{
A a=new B();
a.Sum();
Console.Read();
}
}
执行a.Sum:
- 先检查申明类A 。
- 检查到是虚拟方法 。
- 转去检查实例类B,无重写(这个地方要注意了,虽然B里有实现Sum(),但使用new关键字,所以是“覆盖”,不会被认为是重写) 。
- 转去检查类B的父类A,就为本身 。
- 执行父类A中的Sum方法 。
- 输出结果 I am A Class,I am virtual sum()。
override “重写”
- 要扩展或修改继承的方法、属性、索引器或事件的抽象实现或虚实现,必须使用 override 修饰符。
- 由 override 声明重写的方法称为重写基方法。 重写的基方法必须与 override 方法具有相同的签名。
- 不能重写非虚方法或静态方法。 重写的基方法必须是 virtual、abstract 或 override 的。
class A
{
public virtual void Sum()
{
Console.WriteLine("I am A Class,I am virtual sum().");
}
}
class B : A
{
public override void Sum() // 重新实现了虚函数
{
Console.WriteLine("I am B Class,I am override sum().");
}
}
class Program
{
static void Main(string[] args)
{
A a=new B(); // 定义一个a这个A类的对象.这个A就是a的申明类,实例化a对象,B是a的实例类
a.Sum();
Console.Read();
}
}
执行a.Sum:
- 先检查申明类A。
- 检查到是虚拟方法。
- 转去检查实例类B,有重写的方法。
- 执行实例类B中的方法。
- 输出结果 I am B Class,I am override sum()。
new 和override
1、 不管是重写还是覆盖都不会影响父类自身的功能。
2、当用子类创建父类的时候,如 A c = new B(),重写会改变父类的功能,即调用子类的功能;而覆盖不会,仍然调用父类功能。
3、虚方法、实方法都可以被覆盖(new),抽象方法(还没实现,覆盖个寂寞)、接口不可以。
4、抽象方法(用重写来实现)、接口、虚方法可以被重写(override),实方法不可以。
5、重写使用的频率比较高,实现多态;覆盖用的频率比较低,用于对以前无法修改的类进行继承的时候。
abstract “抽象”–只能被继承、重写
abstract定义的类为抽象类,往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。只能作为基类,不能被实例化(因为只是抽象概念,可能包含抽象方法(只定义不实现))。
- 抽象类可以包含抽象方法,也可以包含非抽象方法。但:抽象方法所在的类必须为抽象类。
- 抽象类不能定义为密封类(sealed)
- 抽象类中的抽象属性和抽象方法必须是公有的,因此必须有public修饰符。不能使用virtual、static、private修饰符。
- 抽象类中可以有私有的非抽象属性和方法,但私有的话子类就不能访问,无意义,所以一般情况下都设置为公有。
- 子类必须override抽象类中的所有抽象属性和抽象方法,如果没有全部override,那么子类必须是抽象类
public abstract class Fruit
{
public string vendor { get; set; } //默认为private
public abstract float Price { get; } //抽象属性必须是公有的
public abstract void GrowInArea(); //抽象方法必须是公有的
}
public class Apple : Fruit //子类继承抽象类,需要override抽象类中的抽象属性和抽象方法,如果有未override的,则子类也必须为抽象类
{
public override float Price
{
get
{
if (vendor == "红富士")
return 100;
else
return 0;
}
}
public override void GrowInArea()
{
Console.WriteLine("我在南方北方都能生长,我的生产商是:" + vendor + ",我现在的价格是:" + Price);
}
}
static “静态”–独立团
- 静态类可以理解为一个全局的密封库。只有静态成员,不实例化,可直接调用。不可继承(老祖宗了,谁都能拿来用,不需要继承)。
- 静态类具有两个方面的意义:首先,它防止程序员写代码来实例化该静态类;其次,它防止在类的内部声明任何实例字段或方法。
- 非静态类可以包含静态的方法、字段、属性或事件;
静态类与私有构造函数区别:
- 私有构造器方式仍然可以从类的内部对类进行实例化,而静态类禁止从任何地方实例化类,其中包括从类自身内部。
- 使用私有构造器的类中,是允许有实例成员的,编译器不允许静态类有任何实例成员。
- 使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员,编译器将保证不会创建此 类的实例。
- C#编译器会自动把它标记为sealed。这个关键字将类指定为不可扩展;换言之,不能从它派生出其他类。
静态成员(成员属性、成员方法)
- 静态类第一次加载的时候,这个类下面的所有静态成员都会被加载。
- 静态成员只被创建一次,所以:无论对一个类创建多少个实例对象,它的静态成员都只有一个副本;实例对象有多少个,非静态成员(实例成员)就有多少份。
- 类加载的时候,所有的静态成员就会被创建在“静态存储区”里面,一旦创建,直到程序退出才会被回收。所以,属性需要被共享、方法需要被反复调用的时候,就可以把这些成员定义为静态成员。静态成员是不属于特定对象的成员;
- 在非静态方法中,可以调用静态成员,因为这个时候静态成员肯定存在。
- 在静态方法中,不能直接访问、调用实例成员,因为静态方法被调用的时候,对象还有可能不存在。可以在被实例方法调用的情况下,实例成员做为参数传给静态方法;
- 静态方法可以间接调用实例方法,首先要创建一个类的实例,然后通过这一特定对象来调用静态方法。
- this/base关键字在静态方法中不能使用,因为有可能对象还不存在。
- 可以创建一个类的实例对象,指定对象的成员在静态方法中操作。静态方法是不属于特定对象的方法。
- 静态方法只能被重载,而不能被重写,因为静态方法不属于类的实例成员;
- const字段的行为在本质上是静态的。这样的字段属于类,不属于类的实例。
静态构造函数
- 静态类可以有静态构造函数,静态构造函数不可继承;
- 静态构造函数可以用于静态类,也可用于非静态类;
- 静态构造函数无访问修饰符、无参数,只有一个 static 标志;
- 静态构造函数不可被直接调用,当创建类实例或引用任何静态成员之前,静态构造函数被自动执行,并且只执行一次。
class Program
{
public static int i =0;
public Program()
{
i = 1;
Console.Write("实例构造方法被调用");
}
static Program()
{
i = 2;
Console.Write("静态构造函数被执行");
}
static void Main(string[] args)
{
Console.Write(Program.i);//结果为2,首先,类被加载,所有的静态成员被创建在静态存储区,i=0,接着调用了类的成员,这时候静态构造函数就会被调用,i=2
Program p = new Program();
Console.Write(Program.i);//结果为1,实力化后,调用了实例构造函数,i=1,因为静态构造函数只执行一次,所以不会再执行。
}
}
静态变量
静态全局变量
定义:在全局变量前,加上关键字 static 该变量就被定义成为了一个静态全局变量。
特点:
1. 该变量在全局数据区分配内存。
2. 初始化:如果不显式初始化,那么将被隐式初始化为0。
静态局部变量
定义:在局部变量前加上static关键字时,就定义了静态局部变量。
特点:
1. 该变量在全局数据区分配内存。
2. 初始化:如果不显式初始化,那么将被隐式初始化为0。
3. 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或 语句块结束时,其作用域随之结束。
class Program
{
static int i = getNum();
int j = getNum();
static int num = 1;
static int getNum()
{
return num;
}
static void Main(string[] args)
{
Console.WriteLine("i={0}", i);
Console.WriteLine("j={0}", new Program().j);
Console.Read();
}
}
分析:
Console.WriteLine(“i={0}”, i);
这里 i 是 static 变量,在类 Program 第一次被加载时,要先为 Program 里面所有的 static 变量分配内存。尽管现在有超线程技术,但是指令在逻辑上还是逐条的按顺序自上而下执行,所以 先为 static int i 分配内存,并且在该内存中保持int的缺省值0,接着再为 static int num 变量分配内存,值当然也为0。
然后第二步,为变量赋值:先为 static int i 变量赋值,i=getNum(),看 getNum() 里面的代码,就是return num,这个时候 num 的值是 0 ,于是 i=0 。然后对变量num赋值,num=1;这行代码执行后,num就为1了。所以,j=1。
所以最后的结果为:i=0 j=1
sealed “密封”
若要确定是否密封类、方法或属性,通常应考虑以下两点:
- 派生类通过可以自定义类而可能获得的潜在好处。
- 派生类可能采用使它们无法再正常工作或按预期工作的方式来修改类的可能性。
密封类
应用于某个类时,sealed 修饰符可阻止其他类继承自该类。 类似于Java中final关键字。
将 abstract 修饰符与密封类结合使用是错误的,因为抽象类必须由提供抽象方法或属性的实现的类来继承。
在下面的示例中,类 B 继承自类 A,但没有类可以继承自类 B。
class A {}
sealed class B : A {}
密封重写
对虚方法或虚属性所作的重写进行密封,也就是同override一起使用,允许被继承但防止子类重写特定的虚方法或虚属性。
应用于方法或属性时,sealed 修饰符必须始终与 override 结合使用(即只能密封对虚方法或虚属性的重写)。如果不是虚方法或虚属性会报出错误:cannot be sealed because it is not an override。
在下面的示例中,Z 继承自 Y,但 Z 无法替代在 X 中声明并在 Y 中密封的虚函数 F。
class X
{
protected virtual void F() { Console.WriteLine("X.F"); }
protected virtual void F2() { Console.WriteLine("X.F2"); }
}
class Y : X
{
sealed protected override void F() { Console.WriteLine("Y.F"); }
protected override void F2() { Console.WriteLine("Y.F2"); }
}
class Z : Y
{
// Attempting to override F causes compiler error CS0239.
// protected override void F() { Console.WriteLine("Z.F"); }
// Overriding F2 is allowed.
protected override void F2() { Console.WriteLine("Z.F2"); }
}
extern
https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/extern
修饰符用于声明在外部实现的方法。extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与 Dll Import 属性一起使用;在这种情况下,该方法还必须声明为 static,如下面的示例所示:
[DllImport("avifil32.dll")]
private static extern void AVIFileInit();
- extern 关键字还可以定义外部程序集别名,使得可以从单个程序集中引用同一组件的不同版本。 有关详细信息,请参阅外部别名。
- 将abstract 和 extern 修饰符一起使用来修改同一成员是错误的做法。 使用 extern 修饰符意味着方法是在 C#代码的外部实现的,而使用 abstract 修饰符意味着类中未提供方法实现。
- extern 关键字用于 C# 中时会比用于 C++中时受到更多的限制。 若要比较 C# 关键字与 C++ 关键字,请参见 C++ 语言参考中的“使用 extern 指定链接”。
示例1
程序接收来自用户的字符串并将该字符串显示在消息框中。 程序使用从 User32.dll 库导入的 MessageBox 方法:
//using System.Runtime.InteropServices;
class ExternTest
{
[DllImport("User32.dll", CharSet=CharSet.Unicode)]
public static extern int MessageBox(IntPtr h, string m, string c, int type);
static int Main()
{
string myString;
Console.Write("Enter your message: ");
myString = Console.ReadLine();
return MessageBox((IntPtr)0, myString, "My Message Box", 0);
}
}
示例2
此示例阐释了调入 C 库(本机 DLL)的 C# 程序。
- 创建以下 C 文件并将其命名为 cmdll.c:
// cmdll.c
// Compile with: -LD
int __declspec(dllexport) SampleMethod(int i)
{
return i*10;
}
- 从 Visual Studio 安装目录打开 Visual Studio x64(或 x32)本机工具命令提示符窗口,并通过在命令提示符处键入“cl -LD cmdll.c”来编译 cmdll.c 文件。
参考:https://www.cnblogs.com/dyllove98/archive/2013/07/03/3170153.html
- 在相同的目录中,创建以下 C# 文件并将其命名为 cm.cs:
// cm.cs
using System;
using System.Runtime.InteropServices;
public class MainClass
{
[DllImport("Cmdll.dll")]
public static extern int SampleMethod(int x);
static void Main()
{
Console.WriteLine("SampleMethod() returns {0}.", SampleMethod(5));
}
}
- 从 Visual Studio 安装目录打开一个 Visual Studio x64(或 x32)本机工具命令提示符窗口,并通过键入以下内容来编译 cm.cs 文件,这将创建可执行文件 cm.exe。
“csc cm.cs”(针对 x64 命令提示符)或“csc -platform:x86 cm.cs”(针对 x32 命令提示符)
- 运行 cm.exe。 SampleMethod 方法将值 5 传递到 DLL 文件,这将返回该值与 10 相乘后的结果。 该程序生成以下输出:
SampleMethod() returns 50.
partial
partial 表示把一个class写在多个cs文件中, 编译的时候自动合并。
public class B
{
}
public class C
{
}
public partial class A : B
{
...
}
public partial class A : C
{
...
}
使用Partial需要注意以下一些情况:
1. 使用partial 关键字表明可在命名空间内定义该类、结构或接口的其他部分,同一类型的各个部分的所有分部类型定义都必须在同一程序集和同一模块,分部定义不能跨越多个模块;
2. 所有部分都必须使用partial 关键字;
3. 各个部分必须具有相同的可访问性,如public、private 等;
4. 如果将任意部分声明为抽象的,则整个类型都被视为抽象的;
5. 如果将任意部分声明为密封的,则整个类型都被视为密封的;
6. 如果任意部分声明继承基类时,则整个类型都将继承该类;
7. 各个部分可以指定不同的基接口,最终类型将实现所有分部声明所列出的全部接口;
8. 在某一分部定义中声明的任何类、结构或接口成员可供所有其他部分使用;
9.嵌套类型可以是分部的,即使它们所嵌套于的类型本身并不是分部的也如此。