类(Class)为动态创建的类实例(亦称为“对象”)提供了定义。
新类使用类声明进行创建。
- 标头,指定了类的特性和修饰符(默认访问标识符是
internal
)、类名、基类(若指定)以及类实现的接口。 - 类主体,由在分隔符
{ }
内编写的成员声明列表组成。
当无法再访问对象时,对象占用的内存会被自动回收,无法显式解除分配对象。
类实例是使用 new
运算符进行创建,此运算符为新实例分配内存,调用构造函数来初始化实例,并返回对实例的引用。
局部类型 partial
partial
是一个上下文关键词,只有和 class
、struct
、interface
放在一起时才有含义。其定义的目标可以在多个地方被定义,最后编译的时候会被当作一个类来处理。
- 同一个类型的各个部分必须都有修饰符 partial。
- 使用局部类型时,一个类型的各个部分必须位于相同的命名空间中。
- 一个类型的各个部分必须被同时编译。
- 通常在WPF中会使用局部类型
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
}
}
基类
类继承其基类的成员,隐式包含其基类的所有成员(实例和静态构造函数以及终结器除外)。 派生类可以添加新成员,但无法删除继承成员的定义。
可以将类类型隐式转换成其任意基类类型。 因此,类类型的变量可以引用该类的实例或其派生类的实例。
- 无继承的类均继承自
object
。 - 子类实例转成基类时,子类的特有属性会隐藏起来但不会被清除,若再转回子类则又会直接获得该属性。
成员
类成员要么是静态成员,要么是实例成员。 静态成员属于类,而实例成员则属于对象(类实例)。
访问标识符
访问标识符限制了可以访问类成员的程序文本区域
- public:访问不受限;
- protected:只有在该类或该类的派生类中被访问,有助于实现继承。
- internal:访问限于当前程序集(即不能被其他项目访问)
- protected internal:访问限于包含类、派生自包含类的类或同一程序集中的类
- private:只有该类(或该类中嵌套的派生类)中的函数可以访问。(类成员的默认访问修饰符即为
private
)
类型参数与泛型
在类名后用<>
定义类型参数(通常使用T
),并在类主体中使用这些类型来定义类成员。 这个类称为泛型类。 结构、接口和委托类型也可以是泛型。 泛型类实例化时,必须为每个类型参数指定类型:
public class Pair
{
public TFirst First;
public TSecond Second;
}
...
Pair pair = new Pair { First = 1, Second = "two" };
int i = pair.First; // TFirst is int
string s = pair.Second; // TSecond is string
为泛型的类型参数添加约束
- 通过使用 where 上下文关键字指定约束。
- 如果要对泛型成员执行除简单赋值之外的任何操作或调用
System.Object
不支持的任何方法,则必须对该类型参数应用约束。 - 约束可以为
notnull
,struct
,class
,unmanaged
(非托管类型,即非引用且不包含引用),new()
(必须具有公共无参数构造函数),<基类名>,<接口名>,(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数)。
class Test
where U : struct
where T : Base, new()
{ }
泛型类的继承
泛型类可以继承非泛型类,但非泛型类不能继承泛型类。
继承泛型类的泛型类必须指定作为基类型上约束超集或表示这些约束的约束:
字段
字段是与类或类实例相关联的变量。
- 使用
static
修饰符声明静态字段。 静态字段只指明一个存储位置。 无论创建多少个类实例,永远只有一个静态字段副本。 - 不使用
static
修饰符声明实例字段。 每个类实例均包含相应类的所有实例字段的单独副本。 - 使用
readonly
修饰符声明只读字段。 只能在字段声明期间或在同一个类的构造函数中向只读字段赋值。
方法
在 C# 中,每个执行的指令均在方法的上下文中执行。 Main
方法是每个 C# 应用程序的入口点,并在启动程序时由公共语言运行时 (CLR) 调用。
静态方法通过类进行访问。 实例方法通过类实例进行访问。
参数列表,用于表示传递给方法的值或变量引用。
返回类型,用于指定返回值的类型。 如无返回值,则为 void
。
每个类中,方法的签名必须是唯一的。 (方法签名包含方法名称、类型参数数量及其参数的数量、修饰符和类型。 方法签名不包含返回类型)
类型参数与泛型
方法可能也包含一组类型参数,必须在调用方法时指定类型自变量,这一点与类型一样。 与类型不同的是,通常可以根据方法调用的自变量推断出类型自变量,无需显式指定。
//实现了任意类型组拼成字符串的方法,可以是int,double,string等类型
public static string GetSum(T a,T b){
return a + "" + b;
}
Console.WritrLine(GetSum(12,34));
参数传递方式
- (按)值(传递)参数 (默认)
实参和形参使用的是两个不同内存中的值,当值类型形参的值发生改变时,不会影响实参的值 ; 但引用类型会受到影响 (同JS)。
可以指定默认值,从而省略相应的自变量,这样值参数就是可选的。 - 引用参数
ref
在方法执行期间,引用参数指明的存储位置与自变量相同,当形参的值发生改变时,同时也改变实参的值。
int width = 100;
void ChangeAttr(ref int attr,int target)
{
Console.WriteLine(attr);//100
attr = target;
}
...
ChangeAttr(ref width, 60);
Console.WriteLine(width);//60
- 按输出传递参数
out
类似ref
,区别在于不要求向调用方提供的自变量显式赋值,且必须由方法重新赋值
int width = 100;
void ChangeAttr(out int attr,int target)
{
//Console.WriteLine(attr);此处不能引用attr,否则报错
attr = target;
}
...
ChangeAttr(out width, 60);
Console.WriteLine(width);//60
- 按输入传递参数
in
传入值不允许在方法内被修改 - 参数数组
使用params
修饰符进行声明,允许向方法传递数量不定的自变量。 参数数组只能是方法的最后一个参数,且必须是一维数组类型。
在调用包含参数数组的方法时,要么可以传递参数数组类型的一个自变量,要么可以传递参数数组的元素类型的任意数量自变量。 在后一种情况中,数组实例会自动创建,并初始化为包含给定的自变量。
public class Console
{
public static void Write(string fmt, params object[] args) { }
public static void WriteLine(string fmt, params object[] args) { }
// ...
}
...
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
//等于执行了以下代码
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine("x={0} y={1} z={2}", args);
局部变量
方法主体内声明的变量称为局部变量
。 局部变量声明指定了类型名称、变量名称以及可能的初始值,不具有访问修饰符。
静态和实例方法
静态方法不能访问类的非静态成员,且方法主体中不能使用this
。
实例方法可以访问静态和实例成员,且可以通过this
访问该方法的调用者。
异步方法
使用async
和 await
运算符实现异步方法。await
运算符仅能在async
方法中使用,在同步方法中则使用.Wait
。
异步方法仅有 Task
、Task
、 或 void
返回类型。 void
返回类型主要用于定义需要 void
返回类型的事件处理程序。 无法等待返回 void
的异步方法,并且返回 void
方法的调用方无法捕获该方法引发的异常。
using System;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
DoSomethingAsync().Wait();
Console.ReadKey();
}
private static async Task DoSomethingAsync()
{
int result = await DelayAsync();
Console.WriteLine("Result: " + result);
}
private static async Task DelayAsync()
{
await Task.Delay(100);
return 5;
}
}
构造函数
构造函数是一种特殊的成员方法,其名称与类相同,且没有任何返回类型。
静态构造函数
用于初始化类的静态数据。没有访问修饰符和参数。
静态构造函数不是程序员调用的,在类被实例化或者静态成员被调用的时候自动调用。
实例构造函数
在创建类的新实例时调用。如果没有为类提供实例构造函数,则会自动提供不含参数的空实例构造函数。
- 实例构造函数可以重载。
- 实例构造函数不能被继承。
- 传入参数以在创建对象时赋初始值,称为参数化构造函数。
属性
属性不指明存储位置,而是包含访问器,用于指定在读取或写入属性值时要执行的语句。
通常类的字段应定义成私有的(以小驼峰命名),并额外定义一个公有的属性(同名大驼峰),作为外界访问私有字段的一个通道。通过这种方式可以过滤并控制对类成员的读写,防止非法赋值且便于维护。
public <返回类型(要与被访问变量的类型相同)> <属性名(不能与被访问变量同名)>
{
get{ return <被访问变量>;}
set{ <被访问变量> = value;}
}
- 同时包含
get
和set
访问器的属性是读写属性,仅包含get
访问器的属性是只读属性,仅包含set
访问器的属性是只写属性。 - get 访问器对应于包含属性类型的返回值的无参数方法。
- set 访问器对应于包含一个
value
参数但不含返回类型的方法。 - 对于自动实现的属性可以赋予初始值。
- 当其中一个存在具体实现时,若存在另一项,则也必须有具体实现。
- 属性的访问器可以是虚的。 如果属性声明包含 virtual、abstract 或 override 修饰符,则适用于属性的访问器。
-
get
、set
可以单独设置访问修饰符。
class Employee
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public int Count {get;private set;}
public byte Age {get;set;} = 18;
}
索引器
索引器允许类或者结构的实例按照与数组相同的方式进行索引其成员。类似于属性,索引器分为读写、只读和只写,且索引器的访问器可以是虚的。
class Program
{
static void Main(string[] args)
{
Number num = new Number();
Console.WriteLine(num[1]);//7
Console.ReadKey();
}
}
public class Number
{
List list = new List(new int[] { 6, 7, 8 });
public int this[int index]//声明索引器
{
get { return list[index]; }
set { list[index] = value; }
}
}
事件
类或对象可以通过事件向其他类或对象通知发生的相关事情。 发送(或引发 )事件的类称为“发布者” ,接收(或处理 )事件的类称为“订阅者” 。
事件声明包括事件关键字 event
,且类型必须是EventHandler
。
在类的方法中通过Invoke
触发事件。
通过为实例添加事件处理程序监听并处理事件
- 使用
+=
添加事件处理程序。 - 使用
-=
删除事件处理程序。
class Program
{
static int changeCount = 0;
static void ListChanged(object sender, EventArgs e)
{
changeCount++;
}
static void Main(string[] args)
{
var e = new E();
e.Changed += new EventHandler(ListChanged);
e.OnChanged();
e.OnChanged();
e.OnChanged();
Console.WriteLine(changeCount);//3
Console.ReadLine();
}
}
class E
{
public event EventHandler Changed;
public void OnChanged() =>
Changed?.Invoke(this, EventArgs.Empty);
}
运算符
通过operator
可以定义三种类型的运算符:一元运算符、二元运算符和转换运算符。 所有运算符都必须声明为 public
和 static
。详见运算符重载。
析构函数(终结器)
析构函数的名称是在类的名称前加上一个波浪形 ~
作为前缀,它没有参数和返回值类型,没有访问修饰符也不能显示调用。
析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源。析构函数不能继承或重载。
实例化
通过new
实例化类,还可以在实例化时改变成员的值。
Test test = new Test();
Test test = new Test { str ="hello" };
this 和 成员访问
在C#中this
用来指代当前对象。
- 类中的非静态成员可以在构造函数或非静态方法中通过
this.成员名
或直接通过成员名
访问。
当成员函数中的形参名跟成员变量名一致时,可以通过this.成员名
进行区分。 - 静态成员无法通过
this
访问,应使用成员名
或类名.成员名
的方式访问。
this
不能在静态方法中被使用。
enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
class Baby
{
public Baby()
{
Console.WriteLine("构造函数执行");
}
~Baby()
{
Console.WriteLine("对象已释放");
Console.ReadKey();
}
public static Day birthday;
public void SetBirthday(Day target)
{
birthday = target;
int i = (int)target;
}
public Day GetBirthady()
{
return birthday;
}
}
class Program
{
static void Main(string[] args)
{
var baby = new Baby();
baby.SetBirthday(Day.Wed);
var baby2 = new Baby();
baby2.SetBirthday(Day.Sat);
Console.WriteLine(Baby.birthday);//Sat
Console.WriteLine(baby.GetBirthady());//Sat
Console.WriteLine(baby2.GetBirthady());//Sat
Console.ReadKey();
}
}
//构造函数执行
//构造函数执行
//Sat
//Sat
//Sat
//对象已释放
//对象已释放
匿名类型
匿名类型可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。每个属性的类型由编译器推断。
匿名类型包含一个或多个公共只读属性。 包含其他种类的类成员(如方法或事件)为无效。 用来初始化属性的表达式不能为 null
、匿名函数或指针类型。
var v = new { Amount = 108, Message = "Hello" };
var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};