C#程序设计教程-语法速览

C#程序设计教程-语法速览

《C#程序设计教程-第二版-蒙祖强-清华大学出版社》
C# 介绍 - C# 指南 | Microsoft Docs

学校定的C#教程,从零开始那种。
从开始到P130是基础部分,P130-P250是GUI部分,P250往后是高级特性和程序设计应用。
基础部分比较像C++和Java的杂交语法,可以光速看完。
然后把其中一些特性或者生疏点在此摘记一下。

P31 : 2.5.2-枚举类型

定义

1
2
3
enum weekdays {Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday}
weekdays day;
day = weekdays.Sunday;

枚举类型在定义时按照从左到右的顺序依次给枚举元素分配了整数值:0, 1, 2, … 。利用这些值,可以灵活访问枚举类型变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(String[] args)
{
weekdays day;
day = weekdays.Sunday; //Output //index notes
Console.WriteLine(day); // Sunday //0
day++;Console.WriteLine(day); // Monday //1
day++;Console.WriteLine(day); // Tuesday //2
day++;Console.WriteLine(day); // Wednesday //3
day++;Console.WriteLine(day); // Thursday //4
day++;Console.WriteLine(day); // Friday //5
day++;Console.WriteLine(day); // Saturday //6
day++;Console.WriteLine(day); // 7
Console.ReadKey();

P34 : 2.6.3-多维数组

定义

1
2
3
int [ , ] a;
a = new int[2,3];
int [ , ] a = new int[2,3];

定义时赋值

1
2
3
4
int [ , ] a = new int[2,3]{{1,2,3],{4,5,6}};
// 1 2 3
// 4 5 6
// a.Length=6

P52 : 3.6.2-foreach遍历哈希表、枚举

foreach特性和Java、Python3的foreach(int a : a[])、for i in range一致

  • 遍历数组
1
2
3
4
int[] a = {1,2,3,4};
foreach(int i in a){
Console.WriteLine(i);
}
  • 遍历哈希表

Hashtable是命名空间System.Collections中的一个容器,它类似于Python中的字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Collections;

namespace foreachExam
{
class Program
{
static void Main(string[] args)
{
Hashtable ht = new Hashtable();
ht.Add(201001, "张三");
ht.Add(201002, "李四");
ht.Add(201003, "王五");
ht.Add(201004, "赵六");
Console.WriteLine("学号为奇数的学生:");
Console.WriteLine("---------------");
Console.WriteLine("学号 姓名");
foreach(int stuid in ht.Keys)
{
if ((stuid + 1) % 2 == 0)
Console.WriteLine(stuid+" "+ht[stuid]);
}
Console.ReadLine();
}
}
}
  • 遍历枚举

typeof()获取类型的System.Type对象,Enum.GetValues()用于获取指定枚举类型的枚举值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace foreachEnum
{
class Program
{
enum weeddays { Sundays,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday};
static void Main(string[] args)
{
foreach(weeddays item in Enum.GetValues(typeof(weeddays)))
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
}

P62 : 4.1.1-运算符重载

从C++那里学过来的运算符重载。

对象是引用传参。所以最后return new出来的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InmaginaryNumber
{
class Complex
{
double Rp, Ip;//实部和虚部
public double getRp() { return Rp; }//get实部
public double getIp() { return Ip; }//get虚部
public Complex() { Rp = Ip = 0; }//默认构造
public Complex(double Rp, double Ip)//带参构造
{
this.Rp = Rp;
this.Ip = Ip;
}
public static Complex operator +(Complex c1, Complex c2)//二元+重载符
{
Complex c = new Complex(c1.Rp + c2.Rp, c1.Ip + c2.Ip);
return c;
}
public static Complex operator -(Complex c1, Complex c2)//二元-重载符
{
Complex c = new Complex(c1.Rp - c2.Rp, c1.Ip - c2.Ip);
return c;
}
public static Complex operator -(Complex c)//一元-负号重载
{
Complex c2 = new Complex(-c.Rp, -c.Ip);
return c2;
}
public static implicit operator Complex(string s) //隐式string->Complex
{
//trim是清空前后空格,trimEnd从末尾删除i,直到遇到不为i的就结束
s = s.Trim().TrimEnd('i');//'i'截断
s = s.Trim().TrimEnd('*');//'*'截断
string[] digits = s.Split('+', '-');//以'+'或者'-'划分字符
Complex c;
//余下的string显转double,带参构造
c = new Complex(Convert.ToDouble(digits[0]), Convert.ToDouble(digits[1]));
return c;
}
public String putIN() //类属性打包成string输出
{
String output = Convert.ToString(Rp) + "+" + Convert.ToString(Ip) + "*i";
return output;
}
}
class Program
{
static void Main(string[] args)
{
Complex c1 = new Complex();
Complex c2 = new Complex(1, 2);
Console.WriteLine("c1="+c1.putIN());
Console.WriteLine("c2="+c2.putIN());
Complex c3 = "100+200*i";
Console.WriteLine("c3="+c3.putIN());
Complex c4 = c2 - c1 + (-c3);
Console.WriteLine("c4="+c4.putIN());
Console.ReadLine();
}
}
}

P71 : 4.2.3-类的构造和析构

C#有垃圾回收了!!!C++学着点。非必要可不写析构。
另外,C#默认类成员属性私有。
C#没有指针,这里使用类创建单向链表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace linkUsingClass
{
public class link_node //链表节
{
public int data; //数据
public link_node next; //链结
public link_node(int data) { this.data = data; } //带数据构造链表节
}

class Program
{
static void Main(string[] args)
{
link_node head, point, point_bak;
point = new link_node(1);head = point;
point_bak = point; point = new link_node(2); point_bak.next = point;
point_bak = point; point = new link_node(3); point_bak.next = point;
point_bak = point; point = new link_node(4); point_bak.next = point;
point_bak = point; point = new link_node(5); point_bak.next = point;
point_bak = point;
point = head;//指针指回头指针
while (point != null)
{
Console.WriteLine(point.data);
point = point.next;
}
point = head;
Console.ReadKey();
}
}
}
//1,2,3,4,5

P72 : 4.2.4-类的属性

类的属性定义类似于javabean,是对成员变量的一种拓展。
javabean的作用:

1、所有属性为private
2、提供默认构造方法
3、提供getter和setter
4、实现serializable接口,用于对象的序列化和反序列化

我能看到最直接的作用是控制变量的读写权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
class X
{
int x,y,z;
public X() { x = 1;y = 2;z = 3; }
public int atx //可读写
{
get { return x; }
set { x = value; }//value是一种特殊的变量,用于接受赋值参数。
}
public int aty //只读
{
get { return x; }
}
public int atz //只写
{
set { x=value; }
}
}
class Program
{
static void Main(string[] args)
{
X meber=new X();
meber.atx = 100; //x写
Console.WriteLine("x={0}",meber.atx); //x读
//meber_x.aty = 200; 错误,y只读
Console.WriteLine("y={0}",meber.aty); //y写,错误,y只读
meber.atz = 300; //z只写
//Console.WriteLine("z={0}",meber.atz); //z读取,错误,z只写
}
}
}

P76 : 4.2.6-成员方法的四种参数类型

回顾

java传参:基本类型外向内单向传参,数组可修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.Arrays;
class param{
static int[] a={1,3,4,2,5};
static int aa=10000;
static private void change_value(int[] a,int aa){
System.out.println("--形参改变的方法内参数--");
Arrays.sort(a);
aa+=1;
print_array(a,aa);
}
static private void print_array(int[] a,int aa){
for(int i : a){
System.out.print(i+" ");
}
System.out.println(aa);
}
public static void main(String[] args){
change_value(a,aa);
System.out.println("--方法块父级域参数,类静态--");
print_array(a,aa);
int[] b={1,3,4,2,5};
int bb=10000;
change_value(b,bb);
System.out.println("--方法块父级域参数--");
print_array(b,bb);
}
}
/*
--形参改变的方法内参数--
1 2 3 4 5 10001
--方法块父级域参数,类静态--
1 2 3 4 5 10000
--形参改变的方法内参数--
1 2 3 4 5 10001
--方法块父级域参数--
1 2 3 4 5 10000
*/

python传参:基本类型外向内单向传参,数组可修改。

1
2
3
4
5
6
7
8
9
10
aa=[1,2,3,4,5]
a=10000
def change(list_a,int_a):
list_a.append(1)
int_a+=1
print("方法内",list_a,int_a)
change(aa,a)
print("同块 ",aa,a)
# 方法内 [1, 2, 3, 4, 5, 1] 10001
# 同块 [1, 2, 3, 4, 5, 1] 10000

C#的传参

  1. 值类型参数。和常见语言的方法调用参数特性一致(java,python)。基本变量在方法内有自己的生存周期。
  2. 引用型参数ref。有点像C++的传引用,方法内外双向传参。
  3. 输出型参数out。C#新类型,方法块内向外传值,因此方法内必须对值进行初始化。
  4. 输入型参数in。形参成为实参的别名。必须是变量。
  5. 数组型参数params。C#新类型,数组参数可以省去定义变量名,可以一次传入若干变量。

这里in参数书上没提,补充一下:in 参数修饰符 - C# 参考 | Microsoft Docs
ref 修饰符,指定参数由引用传递,可以由调用方法读取或写入。
out 修饰符,指定参数由引用传递,必须由调用方法写入。
in 修饰符,指定参数由引用传递,可以由调用方法读取,但不可以写入。

当我们传递一个数组给一个方法时,参数数组的元素可以被改变,出了方法继续使用,但new出来的数组如果不return的话会被垃圾回收。
这一点我们想到java的传参。实际上,java传参是值传递。C#既有值传递也有引用传递。

java传参:

如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝。

当你new一个新对象赋值给“copy”变量名后,“copy”修改的是new出来的地址。出函数后,“origin”查看的值还是原来的地址。java的传参是值传递。

Java 到底是值传递还是引用传递? - 知乎 (zhihu.com)

一个例子看懂C#传参:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace @ref
{
class Program
{
public static void change_value(int i ,int[] j) //值类型参数,从外到内单向传递
{
i += 10000;
for(int ii = 0; ii < j.Length; ii++)
{
j[ii] += 10000*(ii+1);
}
}
public static void change_ref(ref int i,ref int[] j) //ref引用型参数,外部内部双向传递
{
i += 10000;
for (int ii = 0; ii < j.Length; ii++)
{
j[ii] += 10000 * (ii + 1);
}
}
public static void change_out(out int i,out int[] j) //out型参数,由内到外单向传递
{
//i += 10000; //错误,out参数需要在方法内初始化变量
i = 10000;
j =new int[]{ 10000,20000,30000};
//注:补充,表达式无法作为out输出参数
}
public static void change_params(params int[] j) //数组型参数,和ref参数类似
{
for (int ii = 0; ii < j.Length; ii++)
{
j[ii] += 10000 * (ii + 1);
}
}
public static void change_value(int[] j) //值类型参数,调用方式与以上数组类型参数对比
{
j = new int[] { 10000, 20000, 30000 };//数组j(外部?栈)并没有指向堆中新的数组,因此此处new的j(内部?堆)如果不返回则被垃圾回收
}
public static void output(int i,int[] j) //打印输出
{
Console.WriteLine(i);
foreach(int jj in j)
{
Console.Write("{0} ",jj);
}
Console.WriteLine();
}
public static void init(out int i,out int[] j) //初始化
{
i = 0;
j =new int[]{ 0,0,0};
}

static void Main(string[] args)
{
int i;
int[] j;
init(out i, out j);
Console.WriteLine("原值:");
output(i, j);

init(out i, out j);
change_value(i, j);
Console.WriteLine("常规值参数");
output(i, j);

init(out i, out j);
change_ref(ref i, ref j); //ref型参数需要在函数调用中指明
Console.WriteLine("ref型参数");
output(i, j);

init(out i, out j);
change_out(out i, out j); //out型参数需要在函数调用中指明
Console.WriteLine("out型参数");
output(i, j);

init(out i, out j);
change_params(j); //params型参数
Console.WriteLine("params型参数");
output(i, j);

init(out i, out j);
change_params(0, 0, 0); //prarams的特有写法
// change_value(0, 0, 0); //错误,值类型参数无法直接(0,0,0)
change_value(j); // new出的对象出了方法就失联了
Console.WriteLine("new出的对象出了方法就失联了");
output(i, j);

Console.ReadKey();

}
}
}
/*
原值:
0
0 0 0
常规值参数
0
10000 20000 30000
ref型参数
10000
10000 20000 30000
out型参数
10000
10000 20000 30000
params型参数
0
10000 20000 30000
new出的对象出了方法就失联了
0
0 0 0
*/

P78 : 4.3.1-继承

C#比起C++少了指针和多重继承。赋值兼容大体还是按照C++那套来的。

重载

  1. 不能重载静态成员方法
  2. 方法名相同,参数表不同,不看返回值类型

多态

  1. 编译时多态:方法重载
  2. 运行时多态:重写虚方法

继承

  1. 子类作基类使用时,子类使用从基类继承来的属性和方法,无视隐藏
  2. 子类使用基类的构造方法,构造的是来自基类的属性,可被新同名属性隐藏
  3. 方法里,子类的this和可以复写基类的this。

类被继承后,子类包含:新定义的属性和方法+重写基类的属性和方法+继承来自基类的属性和方法;
重写基类的属性和方法后,子类继承过来的属性和方法不是被覆盖,而是被隐藏。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Success
{
class Shape
{
private String name = "形状"; // private属性不被继承
protected double pi; // protected属性被继承
public Shape() // 基本构造;注意返回值不同无法重载
{
pi = 3.14;
}
public Shape(double pi) // 带参构造,参数列表不同,与无参构造重载
{
this.pi = pi; // 重载函数:编译时多态
}
public void show_name() // public属性被继承
{
Console.WriteLine(this.name);
}
public virtual double circle_area(double len, double pi = 3.14)
{ // 可选参数必须出现在必须参数之后
// todo
// 基类 虚函数 运行时多态
return 0;
}
}
class Circle:Shape
{
private String name = "圆";
// public int pi = 3; // 提示“Circle.pi”隐藏继承成员“Shape.pi”。如果有意隐藏,请使用关键字new
new public double pi=3; // 覆盖基类的pi,属性protected->public,带参构造里的pi因此无效
public double len;
public Circle()
{
pi = 3.1415926535;
len = 1;
}
public Circle(double len,double pi) :base(pi) // 使用基类的带参构造
{
Console.WriteLine("--圆的带参构造--");
Console.WriteLine("pi= {0}", pi); // 此处的pi是base.pi
Console.WriteLine("this.pi= {0}", this.pi);
Console.WriteLine("base.pi= {0}", base.pi);
Console.WriteLine("--------------");
this.len = len;
}
public void show_name() // 方法实际隐藏了基类
{
Console.WriteLine(this.name); // 虽然和基类的show_name()写法上完全相同,但this不是同一对象
}
public override double circle_area(double len, double pi)
{ //基类的虚方法和派生类中重写方法的方法名、参数列表、还有返回值必须完全一致(可选参数可以不同)
double area = pi * len * len;
return area;
}
}
class Program
{
static void show(Shape shape)
{
shape.show_name();
}
static void Main(string[] args)
{
Shape shape = new Shape();
shape.show_name();
show(shape);
Circle circle = new Circle();
circle.show_name();
show(circle); // C++赋值兼容规则:对象切片
Circle circle2=new Circle(10,3.1514);
Shape shape2 = circle2;
shape2.show_name(); //对象切片的显示写法
Console.WriteLine(circle2.len);
Console.WriteLine(circle2.pi);
Console.WriteLine(circle2.circle_area(circle2.len,circle2.pi));
Console.ReadLine();
}
}
}

/*
形状
形状

形状
--圆的带参构造--
pi= 3.1514
this.pi= 3
base.pi= 3.1514
--------------
形状
10
3
300
*/

P86 : 4.5.1-接口

定义:

接口成员不允许修饰符,默认公有成员。
成员可以分为四类:方法、属性、事件和索引器,不包含成员变量。

1
2
3
4
5
6
7
interface I
{
void f(int x); //方法
int att{ get; set; } //属性(可读、可写)
event EventHandler OnDraw; //事件
string this[int index] { get; set; } //索引器
}

C#通过对接口的多重继承实现,来达到C++里多重继承的效果。实现过程需要满足所有方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
interface I1
{
void f(int x); //方法
int att { get; set; } //属性(可读、可写)
event EventHandler OnDraw; //事件
string this[int index] { get; set; } //索引器
}

interface I2
{
void g();
}

public class A : I1, I2
{
private string[] strs = new string[100]; //新增变量
public void g() { } //实现I2方法
public void f(int x) { } //实现I1方法
public event EventHandler OnDraw //实现I1事件
{
add { }
remove { }
}
public int att //实现I1属性
{
get { return 1; }
set { }
}
public string this[int index] //实现I1索引
{
get
{
if (index <=0 || index >= 100) return "";
return strs[index];
}
set
{
if (!(index < 0 || index >= 100)) strs[index] = value;
}
}
}

P88 : 4.6-委托

  • 解释:
    函数指针。

  • 作用:
    方法回调。通俗来说就是:可以让一个方法伪装成一个对象,作为参数被其它方法调用。
    个人感觉在不同类的方法耦合调用的情景下,这东西很方便。
    b.function(new Delegate(A.function));

  • 定义:

    1
    属性 修饰符 delegate 返回类型 委托类型名(参数表);

    属性、修饰符:new、public、internal、protected、private。

  • 实例化:

    1
    委托对象 = new 委托类型 (关联方法);

    以下例子中,fg是a.f的委托;static_fg是A.g的委托。

  • 条件:
    参数表相同,返回类型相同。

  • 组合委托:
    同类型委托之间可以使用 + - 运算;
    必须是同类型的委托才能组合,(同返回类型,同参数表);
    委托列表所有委托统一使用组合委托传入的参数;
    带返回的委托组合在一起时,组合委托返回的是委托列表最后一个委托的返回值。

1
2
3
4
5
6
7
8
9
fun([delegate_fun],s1,s2)
{ | |
"class_a.fun"(s1,s2)
}
----------------------------
fun( object_? ,s1,s2)
{ | |
class_a.fun(s1,s2)
}

如上图,使用委托[上],和传入对象[下]。使用委托仅限对象某一特定方法进入,相对来说更加安全。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace simpledelegate
{
delegate void MyDelegate(string s); //声明委托
class A
{
public void f(string msg)
{
Console.WriteLine("方法f:"+msg);
}
public static void static_f(string msg)
{
Console.WriteLine("方法static_f:"+msg);
}
}
class B
{
public void h(MyDelegate m) //B.h -> MyDelegate(?) -?-> static_f/f/...
{
m("这里是B,使用委托偷来了A的方法" + m.Method.Name);
}
}
delegate string MyDelegateWithReturn(string s); //声明带返回委托
class C
{
public string g(string msg)
{
return "方法g:" + msg;
}
public static string static_g(string msg)
{
return "方法static_g:" + msg;
}
}
class Program
{
static void Main(string[] args)
{
MyDelegate static_fd = new MyDelegate(A.static_f); //MyDelegate(static_fd) -> A.static_f
A a = new A();
MyDelegate fd = new MyDelegate(a.f);
static_fd("A.static_f委托给static_fd");
A.static_f("原来的A.static_f"); // A.static_f
fd("a.f委托给fd"); // MyDelegate(fd) -> a.f
a.f("原来的a.f"); // a.f

B b = new B();
b.h(fd); // B.h -> MyDelegate(static_f) -> static_f
b.h(static_fd); // B.h -> MyDelegate(f) -> f

MyDelegate togather_f = fd + static_fd; // 无返回组合委托
togather_f("无返回委托组合");

C c = new C(); // 带返回组合委托
MyDelegateWithReturn static_fg = new MyDelegateWithReturn(C.static_g);
MyDelegateWithReturn fg = new MyDelegateWithReturn(c.g);
MyDelegateWithReturn togather_g = fg + static_fg; // 组合委托,static_fg是委托列表的最后委托,使用它的return
Console.WriteLine(togather_g("带返回委托组合")); // 如果组合委托列表关联的方法有返回值,委托返回最后一个委托的返回值

//togather_f = fg + fd; //错误 // 必须是同类型的委托才能组合,(同返回类型,同参数表),委托列表所有委托统一使用组合委托传入的参数

Console.ReadLine();
}
}
}

/*
方法static_f:A.static_f委托给static_fd
方法static_f:原来的A.static_f
方法f:a.f委托给fd
方法f:原来的a.f
方法f:这里是B,使用委托偷来了A的方法f
方法static_f:这里是B,使用委托偷来了A的方法static_f
方法f:无返回委托组合
方法static_f:无返回委托组合
方法static_g:带返回委托组合
*/

P96 : 4.7-泛型类

字面意思,支持各种类型的类。

定义一个函数,使其能交换两个参数的值,参数类型包括数组、对象、值等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace generic
{
class Program
{
class B<T> //多用T作为参数
{
public static void swap(ref T x,ref T y) //不定义引用是换不出去的
{
T t = x;
x = y;
y = t;
}
}
static void Main(string[] args)
{
int a, b;
a = 100;b = 200;
Console.WriteLine("交换前:a={0},b={1}",a,b);
B<int>.swap(ref a, ref b);
Console.WriteLine("交换后:a={0},b={1}",a,b);
Console.WriteLine("----------------------");
string s1, s2;
s1 = "西游记";s2 = "红楼live";
Console.WriteLine("交换前:s1={0},s2={1}",s1,s2);
B<string>.swap(ref s1, ref s2);
Console.WriteLine("交换后:s1={0},s2={1}",s1,s2);
Console.ReadKey();
}
}
}
/*
交换前:a=100,b=200
交换后:a=200,b=100
----------------------
交换前:s1=西游记,s2=红楼live
交换后:s1=红楼live,s2=西游记
*/

泛型类用的多的还是泛型数组List类。特性较多,不抄了,直接搬库:

List Class (System.Collections.Generic) | Microsoft Docs

P110 : 4.9-命名空间

1
2
3
4
namespace 命名空间名
{
命名空间成员;
}

命名空间可以是任意合法的标识符,命名空间成员通常是类,还可以是接口、枚举、委托、其它命名空间。
命名空间的修饰符为隐含的public类型,不能在声明时显式指定任何修饰符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Math;

namespace Outlander
{
class A
{
static public void print()
{
Console.WriteLine("this is Outlander.classA.print()");
}
}
}

namespace Here
{
using Out = Outlander.A; //起别名
class A
{
static public void print()
{
Console.WriteLine("this is Here.classA.print()");
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(PI);
Outlander.A.print();
Out.print();
A.print();
Console.ReadLine();
}
}
}
/*
3.14159265358979
this is Outlander.classA.print()
this is Outlander.classA.print()
this is Here.classA.print()
*/

常规导入:using Outlander;
相当于:import Outlander.*;

其它实用方式:

  1. using指令:引入命名空间
1
2
using System;
using Namespace1.SubNameSpace;
  1. using static 指令:无需指定类型名称即可访问其静态成员的类型
1
2
using static System.Math;
Console.WriteLine(PI); // 直接使用System.Math.PI
  1. 起别名
1
using Out =Outlander.A;
  1. using语句:将实例与代码绑定

using 语句 - C# 参考 | Microsoft Docs

1
2
3
using StringReader 
left = new StringReader(numbers),
right = new StringReader(letters);

P120 : 5.1-异常

常用异常类:

介绍
ArithmeticException 在进行算术运算时可能产生的异常,是DivideByZeroException和OverflowException的基类
ArrayTypeMismatchException 但储存元素类型与数组元素类型不匹配而导致储存失败产生此异常
Exception 所有异常类的基类,可捕获所有异常
FormatException 参数格式错误而引发的异常
DivideByZeroException 当用零除一个整数数据时会产生此异常
IndexOutOfRangeException 用一个小于零或者大于数组边界的下标来访问一个数组元素时会产生此异常
IOException 该类用户处理进行文件输入输出操作所引发的异常
NullReferenceException 试图以null作为对象名来引用对象的成员会产生此异常
OutOfMemeryException 当使用new来申请内存而失败时会产生此异常
OverflowException 但选中的上下文中所进行的算术运算、类型运算等而导致存储单元溢出会产生此异常
SqlException SQL操作引发的异常
TypeInitializationException 静态构造函数抛出异常且没有任何catch结构来捕获它时会产生此异常

注意:

  • Message:string类型的只读属性,包含异常原因描述。

  • InnerException:Exception类型的只读属性,如果其值为null,表明异常不是由另一个异常引发,而是由系统内部产生或者根据相关条件抛出;如果不是null,表示当前异常是对另一个异常的回应而被抛出,“另一个异常”则保存在InnerException属性中

  • catch (异常类 对象名){ … }中的 (异常类 对象名)可以省略,即catch { … }。如果省略该部分,不管在try中产生什么异常,程序都会转向执行catch中的代码,这种情况无法获取此异常的任何信息。

  • 当try语句块中可能产生多个异常,遇到第一个异常后直接进入catch。此时若使用多个catch结构捕获,存在catch块顺序上的要求。派生类异常的catch块必须放到基类catch块的前面,因为基类catch块的捕获范围更大。如果两catch块异常同级则顺序随意。

  • 使用try-catch-finally结构时,哪怕catch块最后要执行return语句(try-catch(return)-finally),依然会执行finally块。

异常抛出

  • 直接抛出,异常重发
    throw e;
  • 使用捕获的异常创建新的异常后抛出
    throw new Exception([参数表]);
    —或者—
    Exception 异常对象=new Exception([参数表]);
    throw 异常对象;

直接抛出和二次打包的差别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace @throw
{
class testException
{
public void mistake()
{
int n = 1;
int m = 1;
try
{
n = n / (n - m);
Console.WriteLine("无任何异常");
}
catch(Exception e)
{
throw new Exception("mistake():" + e.Message, e); //二次打包后抛出
//throw e; //直接抛出,异常重发
}
}
}
class Program
{
static void Main(string[] args)
{
testException te = new testException();
try
{
te.mistake();
}
catch (Exception e)
{
Console.WriteLine("当前捕获的异常:"+e.Message); //打包后的异常
Console.WriteLine("当前异常内部异常(原异常)" + e.InnerException.Message); //原异常
//异常如果被throw e直接抛出,而不是经过throw new Exception(e)打包抛出,则不存在所谓这里的打包内部异常InnerException,值为null
}
finally
{
Console.ReadLine();
}
}
}
}
/*
当前捕获的异常:mistake():尝试除以零。
当前异常内部异常(原异常)尝试除以零。
*/

异常的运用实例:用户自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyException
{
class UserException : Exception
{
public UserException() { }
public UserException(string ms) : base(ms) { }
public UserException(string ms, Exception inner) : base(ms, inner) { }
}
class student
{
private string name;
private double score;
public void setInfo(string name,double score)
{
if (name.Length > 8)
{
throw (new UserException("姓名长度超过8个字节"));
}
if (score < 0 || score > 100)
{
throw (new UserException("非法的分数"));
}
this.name = name;
this.score = score;
}
}
class Program
{
static void Main(string[] args)
{
student s = new student();
try
{
s.setInfo("张三",999);
}catch(Exception e)
{
Console.WriteLine("产生异常:{0}",e.Message);
Console.ReadLine();
}
}
}
}
/*
产生异常:非法的分数
*/

P133 : 6.1-简易文本编辑器

文本编辑器包含文件打开和保存的操作,使用到系统的一个轮子:openFileDialog和saveFileDialog。课本代码有误,或只给出部分代码(将部分变量初始化代码写在外层的窗体初始化函数中),或课本过时(2010出版),说明一下:

1
2
3
4
5
6
7
8
9
10
//open file 菜单项
private void openfileToolStripMenuItem_Click(object sender,EventArgs e){
OpenFileDialog open=new OpenFileDialog();
open.Fileter="txt files(*.txt) | *.txt"; //此处注意, | 左右包含一个空格
if(open.showDialog()==DialogResult.OK)
{
richTextBox1.LoadFile(open.FileName,RichTextBoxStreamType.PlainText);
}
}
//...保存项同理

此外,第六章大都是组件和控件的介绍,实在没啥必要精读,用时拖着玩两下就会了,IDE代码补全里的介绍甚至比课本详细。


2021/06/04更新

咳咳,说来真不好意思。平时作业写完要么学高数去了,要么玩游戏去了,没心情更博客了,拖到现在。本来是初学记录的,目前C#大作业都整完了。

剩下还有这些东西要补充的:

P208 : 7.2-目录
P212 : 7.3-文件
P247 : 9.1-多线程
P275 : 10.1-数据库
P312 : 11.1-ASP.NET Web

to be continue…

以后有缘再更到新文章里。
此文完结。
说到大作业,其实我只把目录、文件、数据库看了一遍,多线程和网页ASP没学,重点学了下treeView和DataGridView两个控件,连了下SQL Server,写了个学生管理系统,完了。
这个学期的C#就学到这里。
准备期末答辩了。

以后还是会细说一下C#多线程的。