1.构造函数
是在实例化对象时自动调用的函数。
必须与所属的类同名,且不能又返回类型。
用于初始化字段值,完成对类的初始化
没有返回类型,它可以带参数,也可以不带参数;
使用new创建一个类的新对象时,系统回自动调用给类的构造函数初始化新对象。
a.系统默认的无参构造函数
不带参数的构造函数称为“默认构造函数”。无论何时,只要使用 new 运算符实例化对象,并且不为 new 提供任何参数,就会调用默认构造函数。
除非类是 static 的,否则 C# 编译器将为无构造函数的类提供一个公共的默认构造函数,以便该类可以实例化。
注意一旦类有了自己的构造函数,无论是有参数还是没有参数,默认构造函数都将无效,而且仅仅声名一个类而不实例化它,则不会调用构造函数
class TestClass
{
public TestClass(): base() {}
}
b. 实例构造函数
实例构造函数用于创建和初始化实例。创建新对象时将调用类构造函数
只有在用户用new关键字为对象分配内存时才被调用,而且作为引用类型的类,其实例化后的对象必然时分配在托管堆(Managed Heap)上。
using System;
class Point
{
public double x, y;
//构造一个无参构造函数,但初始化了数据
public Point()
{
this.x = 0;
this.y = 0;
}
//一个有参构造函数
public Point(double x, double y)
{
this.x = x;
this.y = y;
}
}
class Test
{
static void Main()
{
Point a = new Point(); //调用了无参构造函数
Point b = new Point(3, 4); // 用构造函数初始化对象
}
}
c. 私有构造函数
是一种特殊的实例构造函数。它通常用在只包含静态成员的类中。如果类具有一个或多个私有构造函数而没有公共构造函数,则不允许其他类(除了嵌套类)创建该类的实例。
class NLog
{
// Private Constructor:
private NLog() { }
public static double e = System.Math.E; //2.71828...
}
声明空构造函数可阻止自动生成默认构造函数。注意,如果您不对构造函数使用访问修饰符,则在默认情况下它仍为私有构造函数。但是,通常显式地使用 private 修饰符来清楚地表明该类不能被实例化。当没有实例字段或实例方法(如 Math 类)时或者当调用方法以获得类的实例时,私有构造函数可用于阻止创建类的实例。
d. 静态构造函数
- 用于对静态字段、只读字段等的初始化;
- 添加static关键字,不能添加访问修饰符,因为静态构造函数都是私有的
- 类的静态构造函数在给定应用程序域中至多执行一次,只有创建类的实例或者引用类的任何静态成员才激发,不能带又参数
- 静态构造函数是不可继承的,而且不能被直接调用;
- 如果类中包含用来开始执行的 Main 方法,则该类的静态构造函数将在调用 Main 方法之前执行.任何带有初始值设定项的静态字段,则在执行该类的静态构造函数时,先要按照文本顺序执行那些初始值设定项;
- 如果没有编写静态构造函数,而这时类中包含带有初始值设定的静态字段,那么编译器会自动生成默认的静态构造函数;
- 一个类可以同时拥有实例构造函数和静态构造函数,这是惟一可以具有相同参数列表的同名方法共存的情况
class Employee
{
private static DataSet ds;
static Employee()
{
ds = new DataSet(...);
}
...
}
e.在类的层次结构中(即继承结构中)基类和派生类的构造函数的使用方式。
派生类对象的初始化由基类和派生类共同完成:基类的成员由基类的构造函数初始化,派生类的成员由派生类的构造函数初始化。
当创建派生类的对象时,系统将会调用基类的构造函数和派生类的构造函数,构造函数的执行次序是:先执行基类的构造函数,再执行派生类的构造函数。如果派生类又有对象成员,则,先执行基类的构造函数,再执行成员对象类的构造函数,最后执行派生类的构造函数。至于执行基类的什么构造函数,缺省情况下是执行基类的无参构造函数,如果要执行基类的有参构造函数,则必须在派生类构造函数的成员初始化表中指出。
class Program
{
static void Main(string[] args)
{
childTest test = new childTest();
childTest test1 = new childTest(2);
childTest test2 = new childTest(2, 3);
Console.Read();
}
}
class baseTest
{
public baseTest()
{
Console.WriteLine("父类构造函数");
}
public baseTest(int i)
{
Console.WriteLine("父类有参数构造函数"+i);
}
}
class childTest:baseTest
{
public childTest()
{
Console.WriteLine("子类无参数构造函数");
}
public childTest(int i)
{
Console.WriteLine("子类有参数构造函数" + i);
}
public childTest(int a, int b):base(a)
{
Console.WriteLine("子类2个参数构造函数");
}
}
/* base关键字是 调用基类构造函数;this关键字是调用类本身参数不同的构造函数,但遵循从少到多。
* 例如: 一个参数 只能调用一个参数或无参数
* 二个参数能调用二个参数,一个参数或无参数。 递减*/
2.静态函数 静态成员
a.静态类:声明为static,表示它仅包含静态成员。不能用new 创建静态实例。
功能:仅包含静态成员;不能被实例化;是密封的,不能被继承;不能包含实例构造函数
何时使用静态类:不需要将某些方法附加到该类的具体实例。
b.静态成员
- 在静态类中,即使没有创建类的实例,也可以调用该类中的静态方法,字段,属性以及时间。
- 如果创建了该类的任何实例,不能使用实例来访问静态成员。只用类名就可以访问。静态成员为类的任何实例所共享,无论类闯将了多少个实例,一个静态成员在内存中只占有一块区域。
- 静态方法只能访问类的静态字段。
3.析构
析构函数是实现销毁一个类的实例的方法成员。析构函数不能有参数,不能任何修饰符而且不能被调用。由于析构函数的目的与构造函数的相反,就加前缀‘~’以示区别
4.委托
当要把方法传送给其他方法时,需要使用委托。
声明委托时,委托和引用的方法签名需要一致(参数个数,类型,顺序一致;返回类型一致),方法名可不同。注意:委托实例化,注意参数是要使用的参数名,且不带括号。
一旦定义了委托类,就可以实例化它的实例,就像处理一般的类一样,所以也可以把委托的一些实例放在数组中。
当需要调用多个方法时,需要使用多播委托
delegate void DoubleOp(double value);
class MainEntryPoint
{
static void Main()
{
DoubleOp operations = MathOperations.MultiplyByTwo;
operations +=MathOperations.Square;
ProcessAndDisplayNumber(operations,2.0);
ProcessAndDisplayNumber(operations,7.13);
ProcessAndDisplayNumber(operations,1.41);
}
}
class MathOperations
{
public static void MultiplyByTwo(double value)
{
double result=value*2;
Console.WriteLine("Multiplying by 2:{0}gives{1}",value,result);
}
public static void Square(double value)
{
double result=value*value;
Console.WriteLine("Squaring:{0}gives{1}",value,result);
}
static void ProcessAndDisplayNumber(DoubleOp action,double valueToProcess)
{
Console.WriteLine();
Console.WriteLine("ProcessAndDisplayNumber called with value ={0}", valueToProcess);
action(valueToProcess);
}
}
委托运行是逐个运行,所以当其中一个发生异常时,编译就会终止。为了解决此问题,delegate类中定了方法GetInvocationList(),它返回一个delegate对象数组,可以直接使用它来捕获异常。
λ表达式: param => 相同于 delegate(string param)
协变与抗变:
将方法签名与委托类型匹配时,协变和逆变为您提供了一定程度的灵活性。
- 协变允许方法具有的派生返回类型比委托中定义的更多。
- 逆变允许方法具有的派生参数类型比委托类型中的更少。
5.事件
是一种特殊的委托。
C#中使用事件需要的步骤:
1.创建一个委托
2.将创建的委托与特定事件关联3.编写事件处理程序
4.利用编写的事件处理程序生成一个委托实例
5.把这个委托实例添加到产生事件对象的事件列表中去,这个过程又叫订阅事件
C#中事件产生和实现的流程:
1.定义A为产生事件的实例,a为A产生的一个事件
2.定义B为接收事件的实例,b为处理事件的方法
3.A由于用户或者系统产生一个a事件(例如点击一个Button,产生一个Click事件)
4.A通过事件列表中的委托对象将这个事件通知给B
5.B接到一个事件通知(实际是B.b利用委托来实现事件的接收)
6.调用B.b方法完成事件处理
6.接口
- 接口只包含方法、委托或事件的签名。方法的实现是在实现接口的类中完成。不能实例化一个借口,方法中无执行代码。
- 一个接口可从一个或多个基接口继承。
- 当基类型列表包含基类和接口时,基类必须是列表中的第一项。
- 实现接口的类可以显式实现该接口的成员。
- 显式实现的成员不能通过类实例访问,而只能通过接口实例访问
- 不允许声明成员上的修饰符,即使是pubilc都不行,因为接口成员总是公有的,也不能声明为虚拟和静态的。如果需要修饰符,最好让实现类来声明。
- 继承接口的类必须要实现接口中的所有声明的方法。若继承多接口,必须实现所有接口中方法。
public interface IBankAccount
{
void PayIn(decimal amount);
bool Withdraw(decimal amount);
decimal Balance
{
get;
}
}
7.抽象类
使用 abstract 关键字可以创建仅用于继承用途的类和类成员,即定义派生的非抽象类的功能。
- 是特殊的类,不能被实例化。
- 抽象类中包含抽象方法。
- 抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须使用override覆盖它。
- 抽象方法声明不提供实现代码,所以没有方法体,是以;结束。抽象方法实现后,不能更改修饰符。
- 抽象类可以被抽象类所继承,抽象方法可以不覆盖,但其派生类必须覆盖。
当抽象类从基类继承虚方法时,抽象类可以使用抽象方法重写虚函数。
public class D
{
public virtual void DoWork(int i)
{
// Original implementation.
}
}
public abstract class E : D
{
public abstract override void DoWork(int i);
}
public class F : E
{
public override void DoWork(int i)
{
// New implementation.
}
}
8.密封类
可以将类声明为密封类。方法是在类定义中将关键字 sealed 置于关键字 class 的前面
密封类不能用作基类,她不能使抽象类。派生类上的类成员、方法、字段、属性或者时间可以讲该成员声明为密封成员。方法是在类成员声明中将 sealed 关键字置于 override 关键字的前面。
类被sealed意味着 不能被继承;方法被sealed 意味着不能被重写。
使用sealed可以使某个类被继承,但不能重写某个方法。
8.结构
结构是值类型,在stack分配地址。
- 不能从另外结构或者类继承,本身也不能被继承,是隐式的sealed.但可继承接口。
- 没有默认的构造函数,但可以添加构造函数
- 没有析构函数
- 不能有abstract,sealed,protected
- 可以不适用new初始化
- 不能在结构中初始化字段,除非字段被声明为const或static。
- 结构在赋值时进行复制。将结构赋值给新变量时,将复制所有数据,并且对新副本所做的任何修改不会更改原始副本的数据。
不使用new 初始化结构
// Declare a struct object without "new."
class TestCoOrdsNoNew
{
static void Main()
{
// Declare an object:
CoOrds coords1;
// Initialize:
coords1.x = 10;
coords1.y = 20;
// Display results:
Console.Write("CoOrds 1: ");
Console.WriteLine("x = {0}, y = {1}", coords1.x, coords1.y);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: CoOrds 1: x = 10, y = 20
9.应用程序域
应用程序域为隔离正在运行的应用程序提供了一种灵活而安全的方法。
应用程序域通常由运行库宿主创建和操作。有时,您可能希望应用程序以编程方式与应用程序域交互,例如想在不停止应用程序运行的情况下卸载某个组件时。
应用程序域使应用程序以及应用程序的数据彼此分离,有助于提高安全性。单个进程可以运行多个应用程序域,并具有在单独进程中所存在的隔离级别。在单个进程中运行多个应用程序提高了服务器伸缩性。
应用程序域具有以下特点:
- 必须先将程序集加载到应用程序域中,然后才能执行该程序集。
- 一个应用程序域中的错误不会影响在另一个应用程序域中运行的其他代码。
- 能够在不停止整个进程的情况下停止单个应用程序并卸载代码。
- 不能卸载单独的程序集或类型,只能卸载整个应用程序域。
10.受管制的代码
受托管的代码不能直接写内存,是安全的,而非托管代码是非安全代码,可以使用指针操作内存一般的项目使用托管代码都行了,也就是说在程序里面不需要用到非安全代码。