发布于 

c#基础学习

C#编程入门(Brackeys)

b站搬运翻译版

.NET && VSCode

安装
  • 安装.NET框架
  • 安装VSCode
    • VSCode安装C#插件

控制台输入创建新项目命令:

1
dotnot new

启动项目命令:

1
dotnet run

自定义项目启动控制台:

  • Ctrl+Shift+P打开VSCode控制台
  • 选择.NET:Generate Assets For Build and Debug
  • VSCode自动生成launch.json配制文件
    • 修改control属性为externalTerminal
入口程序Program.cs
1
2
Console.WriteLine("Hello, World!");
Console.ReadKey();

How to Program in C#

在Program.cs中,体验一下C#语法的手感:

  • Console.ForegroundColor 设置输出颜色
  • Console.Title 设置窗口标题
  • Console.WriteLine 输出
  • Console.ReadLine 读取用户输入
  • Console.ReadKey 读取用户按键
一篇只用Console的Visual Novel
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
// See https://aka.ms/new-console-template for more information

Console.Title = "俄罗斯轮盘";

// 旁白
void BackVoice(string mes){
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("旁白:"+ mes +"\n");
Console.ReadKey();
}

// 恶魔
void DemonVoice(string mes, Boolean stop = true){
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("恶魔:"+ mes +"\n");
if(stop){
Console.ReadKey();
}
}

// 玩家的声音
void PlayerVoice(string mes){
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine("你:"+ mes +"\n");
Console.ReadKey();
}

// 玩家的回答
string PlayerRes(){
Console.ForegroundColor = ConsoleColor.Yellow;
string res = "";
Console.Write("你:");
while(res.Trim()==""){
res = Console.ReadLine();
}
Console.WriteLine("");
return res;
}

BackVoice("你盯着面前的左轮手枪");
BackVoice("冷汗直流");

DemonVoice("接下来,该你了,宝贝");

BackVoice("你的眼神在左轮手枪和它之间反复徘徊");
BackVoice("有一瞬间,你想直接用手枪崩烂它的笑脸");
BackVoice("但那样做有失风度");
BackVoice("你擦了把头上的汗,骂道:");

string dirtyWord = PlayerRes();

DemonVoice("嘿嘿嘿......");
DemonVoice("很不错的气势");
BackVoice("魔鬼阴沉的发笑");
BackVoice("你攥紧这把沉甸甸的手枪");
BackVoice("手心湿漉漉的,有些打滑");

PlayerVoice("离远点欣赏,小心血喷你一身");

BackVoice("它用漆黑的目光注视着你,仿佛要将你看穿");
PlayerVoice("哼");
BackVoice("你用眼神蔑着它垂涎欲滴的蠢样");
PlayerVoice("反正我都会活下来就是了");

DemonVoice("我很欣赏你的自负...", false);
BackVoice("咔哒......碰!");
PlayerVoice("!!!");
DemonVoice("!!!");
BackVoice("未等它说完,你就叩响了扳机");
BackVoice("......");
BackVoice("没有子弹,你还活着,空枪");
BackVoice("你吐出一口气");
PlayerVoice("那么现在,履行诺言的时候到了");
BackVoice("你将微微颤抖的手放下");
PlayerVoice("我要你去杀一个人");
DemonVoice("哼,看来你很清楚怎么和恶魔打交道。");
DemonVoice("告诉我那个人的名字。");

BackVoice("你紧握着手中的枪");
BackVoice("居于黑暗中的双眼冷静的凝视着远处昏暗的灯光");
BackVoice("缓缓张口,说出了那个人的名字......");

string name = PlayerRes();
DemonVoice("......" + name + "?");
DemonVoice("嘿嘿......");
DemonVoice("你的眼光很不错");
DemonVoice("挑了个折磨起来很有趣的灵魂");


BackVoice("魔鬼的脸上露出阴险的笑容");
BackVoice("它站起身,缓缓后退");
DemonVoice("三天后,一切如你所愿");
BackVoice("它丑陋的身影渐渐与身后的黑暗消融在一起");
DemonVoice("嘿嘿嘿......");
BackVoice("它那古怪而低沉的笑声,也随之变得模糊");
DemonVoice("可怜的羔羊......");
DemonVoice("也许有一天你会后悔这笔交易");
DemonVoice("但至少,你现在根本不知道自己都做了些什么。");
BackVoice("它的身影彻底消失在黑暗之中");
BackVoice("只留下你一人");
BackVoice("还有那回荡在黑暗之中的笑声");

Console.ReadKey();

《C#语言入门详解》——刘铁猛

1. C#语言介绍

编程学习路径:
纵向: 语言 → 类库 → 框架
横向: 命令行 → 桌面程序 → 设备(平板/手机)程序 → Web程序 → 游戏 → …

Visual Studio文档
C#文档

WPF程序初体验

主窗口代码:

title:MainWindow.xaml
1
2
3
4
5
6
7
8
<Window>
<Grid>
<Grid.ColumnDefinitions></Grid.ColumnDefinitions>
<Grid.Background></Grid.Background>
<TextBox x:Name="textBox"></TextBox>
<Button Click="Button_Click"></Button>
</Grid>
</Window>

对Button的Click事件进行监听:

title:MainWidnow.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace HelloWPF
{
public partial class MainWindow : Window
{
// 主窗口程序
public MainWindow()
{
InitializeComponent();
}
// 文本框输入框文本变化事件
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{

}
// 按钮点击事件
private void Button_Click(object sender, RoutedEventArgs e)
{
this.textBox1.Text = "Hello World";
}
}
}

2. C#应用程序介绍

Solution和Project
  • Solution:针对客户需求的解决方案
    • 用户需要制作一个管理系统
  • Project: 解决具体问题采取的方案
    • 数据存储?数据库项目
    • 数据读取/表单处理?服务器项目
    • 网站操作?客户浏览器应用项目
    • 手机操作?手机端应用项目
    • 平板操作?平板应用项目
各类C#编写的应用程序
Console

Console App 控制台程序

1
Console.WriteLine("Hello World");
Windows Forms(Old)

WinFormsApp 旧版本桌面应用程序

1
2
3
4
5
6
7
8
9
10
namespace HelloWinFormsApp{
public partial class Form1 : Form{
public Form1(){
InitializeComponent();
}
private void HelloButton_Click(object sender, EventArgs e){
this.HelloTextBox.Text = "Hello World";
}
}
}
color:dark
color:dark
WPF(Windows Presentation Foundation)

WPF 新版桌面应用程序

  • ASP.NET Web Forms(Old) 网站应用
  • ASP.NET MVC(Model-View-Controller) 网站应用
  • Windows Store Application 平板应用
  • Windows Phone Application 手机应用
  • Cloud(Windows Azure) 云计算平台
  • WF(Workflow Foundation)工作流
  • WCF 纯网络服务

3. 类与名称空间

class && namespace
  • c#中,程序作为类实现,因此需要将入口函数包在类中
  • 命名空间
    • 方便实现类间的依赖关系
    • 防止命名冲突
类库引用
  • DLL(Dynamic Link Library 动态链接库/类库)引用
    • 黑盒引用,无源代码
    • NuGet,自动化维护类依赖关系
  • 项目引用
    • 白盒引用,有源代码
项目引用

下面是一个类库(.NET Framework)的主代码

title:Calculator类
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Tools
{
public class Calculator
{
// 加法
public static double Add(double a, double b) { return a + b; }
// 减法
public static double Sub(double a, double b) { return a - b; }
// 乘法
public static double Mul(double a, double b) { return a * b; }
// 除法
public static double Div(double a, double b) {
if (b == 0)
{
return Double.PositiveInfinity;
}
else {
return a / b;
}
}
}
}


在需要引用该类的主项目所在的Solution中,将类库项目添加进去,引入类库依赖,
之后再主项目中使用该类:

title:Program.cs
1
2
3
4
5
6
7
8
9
using Tools;
namespace ConsoleApp{
internal class Program{
static void Main(string[] args){
double result = Calculator.Add(1.14, 4.15);
Console.WriteLine(result);
}
}
}

4. 类、对象、类成员简介

实例和对象

两者的区别主要在语境上:

  • 在讨论类在现实世界中的对应时,一般称为对象
  • 在讨论类在程序中的对应时,一般称为实例

C#中类的三要素:

  • 属性 Property
  • 方法 Function
  • 事件 Event

Visual Studio中,将光标放在类上,点击F1,会自动跳转到类对应的MSDN文档上

使用Event实现桌面时钟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Windows.Threading;
public partial class MainWindow : Window{
public MainWindow(){
InitializeComponent();
// 创建定时器
DispatchTimer timer = new DispatchTimer();
// 配置定时器间隔时间
timer.Interval = TimeSpan.FromSeconds(1);
// 定时事件
timer.Tick += Time_Tick;
// 开启定时器
timer.Start();
}
// 定时事件
private void Time_Tick(object? sender, EventArgs e){
this.TimeTextBox.Text = DateTime.Now.ToString()
}
}
静态成员与实例成员
  • 静态成员:类的成员
  • 实例成员:对象的成员

5. 构成C#语言的基本元素

变量类型
1
2
3
4
5
6
7
8
9
10
11
12
int x_int = 2; // 32bit 
long y_long = 3L; // 64bit 长整型
float x_float = 3.0F; // 32 // 单精度
double y_double = 40.0; // 64 // 双精度

char c = '1'; // 单字符
string str = "12323"; // 字符串
bool flag = true; // 布尔值

string isNull = null; // null

var thisIsNumber = 3; // 推断类型

除了这些静态类型,
C#中还提供2中比较特殊的类型:

  • var 可以为任意基础类型
  • dynamic 动态类型

6. 类型、变量与对象

C#变量、堆、栈、垃圾回收
    • 存储实例
    • 分配内存大
    • 分配不合理会导致内存泄漏
    • C#提供垃圾回收机制,防止内存泄漏
    • 用于存储运行的函数
    • 可能会导致栈溢出
Performance Monitor 性能监视器

使用Performance Monitor,能够监控进程的堆内存使用量。

Win+R → perfmon → 性能监视器 → +号 → Process → Private Bytes(已分配字节数)→ 选定对象实例(要监控的程序)→ 添加 → 双击实例(进行图表定制)

C#五大数据类型

C#的所有数据类型都以Object为基类

  • 引用类型
    • 类 class
    • 接口 interface
    • 委托 delegate
  • 值类型
    • 结构体 struct
      • string、char
      • bool、true、false
      • byte、int、long、float、double、sbyte、short、uint、ulong、ushort、decimal
      • void、null、var、dynamic
    • 枚举 enum
7种变量类型
  • 静态变量 (const修饰符)
  • 实例变量(成员变量,字段)(public/static/private修饰符)
  • 数组变量
  • 值参数
  • 引用参数 (ref修饰符)
  • 输出形参 (out修饰符)
  • 局部变量

7. 运算符

运算符实际上是对函数的一种语法糖,
定义一个Person类的相加方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
public string Name;
public static List<Person> operator +(Person p1, Person p2) {
List<Person> children = new List<Person>();
int childrenNumber = 11;
for (int i = 0; i < childrenNumber; i++) {
Person child = new Person();
child.Name = p1.Name + " 和 " + p2.Name + "的第" + (i+1) + "个孩子";
children.Add(child);
}
return children;

}
}

调用加法的效果:

1
2
3
4
5
6
Person man = new Person();
Person woman = new Person();
man.Name = "亚当";
woman.Name = "夏娃";
//List<Person> children = Person.GetMarry(man, woman);
List<Person> children = man + woman;
基本运算符

点运算符:

1
System.IO.File.Create("D:\\CS_Demo\\test\\HelloWorld.txt");

函数调用符/事件委托

1
2
Action sayHello = new Action(Person.SayHello);
sayHellow()
1
2
3
4
5
class Person{
public static void sayHello(){
Console.WriteLine("hello");
}
}

数组访问

1
int[] myIntArray = new Int[] {1,2,3,4,5};

字典访问

1
2
3
4
5
6
7
8
9
10
Dictionary<string, Person> PersonDic = new Dictionary<string, Person>();
// 字典的添加
for(int i = 1; i< 10;i++){
Person p = new Person();
p.Name = "p_"+i.ToString();
p.Age = i * 10;
PersonDict.Add(p.Name, p);
}
// 字典的访问
Person p_3 = PersonDict["p_3"];

类型type操作符

1
2
3
4
Type t = typeof(int);
Console.WriteLine(t.Namespace);
Console.WriteLine(t.FullName);
Console.WriteLine(t.Name);

default操作符:获取到此类型存储内存全部刷成0的值

1
int a = default(int)
new操作符

功能1:创建实例并将实例的地址转交给变量

1
Form myForm = new Form()

功能2:实例初始化

1
Form myForm = new Form(){Text="Hello",FormBorderStyle = FormBorderStyle.SizableToolWindow}

功能3: 创建匿名类型

1
2
var person = new {Name = "Mr.Okay", Age = 20};
Console.WriteLine(person.GetType().Name); // f__AnomymousType0`2

功能4:子类隐藏父类方法

1
2
3
4
5
6
7
8
9
10
class Student {
public void Report(){
Console.WriteLine("i'm a student");
}
}
class CS_Student:Student{
new public void Report(){
Console.WriteLine("i'm a cs student");
}
}

什么是依赖注入?
依赖注入又是如何对程序进行解耦合的?

checked && unchecked

用于检查某个值的溢出情况,
C#中默认进行unchecked,

进行变量的checked检查:

1
2
3
4
5
6
uint x = uint.MaxValue;
try{
uint y = checked(x+1); //System.OverflowException:“算术运算导致溢出。”
}catch(OverflowExcept ex){
Console.WriteLine("overflow error");
}

进行代码块的checked检查

1
2
3
4
5
6
7
checked{
try{
uint y = x+1;
}catch(OverflowExcept ex){
Console.WriteLine("overflow error");
}
}
delegate

delegate是声明匿名方法的操作符,

比如:某个函数声明后,实际只使用了一次:

1
2
3
4
5
6
7
public MainWindow(){
this.myButton.Click += MyButton_Click;
}

public void MyButton_Click(){
this.myTextBox.Text = "Hello World";
}

这时可以使用delegate来直接声明(现在多被lambda函数取代)

1
2
3
this.myButtonBox.Click += delegate (object sender, RoutedEventArgs e){
this.myTextBox.Text = "Hello World";
}

lambda表达式的方式更加简单,并且程序会对参数类型进行推断

1
2
3
this.myButton.Click += (sender, e) => {
this.myTextBox.Text = "Hello World";
}
指针

使用指针时,代码块需要开启unsafe模式,
并且需要进行额外配置:
项目/Oporator属性/生成/允许不安全代码(勾选)

1
2
3
4
5
6
7
8
9
10
11
unsafe {
Student stu = new Student(){Name = "Jason", Age = 18};
// 指针符号 *
// 取地址符号&
Student * pStu = &stu;
// 箭头符号
pStu=>Age = 20;
// 取引用符号
(*pStu).Name = "Tim";

}
类型转换操作
  • 隐式类型转换
    • 不会影响数值精度的情况下完成转换(低精度转高精度)
    • 子类向父类的转换
  • 显式类型转换
    • 可能丢失精度的转换,cast转换
    • Convert调用
    • ToString/Parse/TryParse
子类转父类
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
internal class Program
{
static void Main(string[] args)
{
Programmer programmer = new Programmer();
// 子类转父类
Human human = programmer;
Animal animal = programmer;
human.Think();
human.Eat();
}
}

class Animal {
public void Eat() {
Console.WriteLine("Eating");
}
}
class Human : Animal {
public void Think() {
Console.WriteLine("Thinking");
}
}
class Programmer: Human {
public void Code() {
Console.WriteLine("Coding");
}
}
cast转换
1
2
3
4
uint x = ushort.MaxValue + 1;
ushort y = (ushort)x;
Console.WriteLine(x); // 65536
Console.WriteLine(y); // 0
Parse/TryParse

Parse在无法转换时,会抛出错误

1
2
3
4
5
string x = Console.ReadLine();
string y = Console.ReadLine();
double x_value = double.Parse(x);
double y_value = double.Parse(y);
Console.WriteLine(x_value + y_value);

对应TryParse,接收无法转换的值不会报错,需要一个out参数:

1
2
3
4
5
6
string x = Console.ReadLine();
string y = Console.ReadLine();
double x_value, y_value;
double.TryParse(x, out x_value);
double.TryParse(y, out y_value);
Console.WriteLine(x_value + y_value);
自定义类实例转换

通过在类中定义带关键字前缀函数,实现类型转换:

  • 显示转换 explicit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Stone stone = new Stone();
stone.Age = 5000;
Monkey wukong = (Monkey)stone; // 显式转换
Console.WriteLine(wukong.Age);

class Stone {
public int Age;
// 定义转换操作方法
public static explicit operator Monkey(Stone stone) {
Monkey m = new Monkey();
m.Age = stone.Age/500;
return m;
}
}
class Monkey {
public int Age;
}
  • 隐式转换 implicit
1
2
3
4
5
6
7
8
9
10
11
// 隐式转换
Monkey m = stone
class Stone {
public int Age;
public static implicit operator Monkey(Stone stone) {
Monkey m = new Monkey();
m.Age = stone.Age/500;
return m;
}
}

浮点数除法|正负无穷大

浮点数的除数可以是0,结果会是正负无穷大

1
2
3
4
5
6
double x = -5.0;
double y = 0;
double z = x / y;
Console.WriteLine(z); // -Infinity
Console.WriteLine(double.PositiveInfinity / double.NegativeInfinity); // NaN
Console.WriteLine(double.PositiveInfinity * 0.0); // NaN
字符比较大小

比较ASCII编码大小

1
2
3
4
5
6
char char1 = 'A';
char char2 = 'a';
ushort u1 = (ushort)char1;
ushort u2 = (ushort)char2;
Console.WriteLine(char1 + " = " + u1); // 65
Console.WriteLine(char2 + " = " + u2); // 97
is | as

is实现类的检测

1
2
3
4
5
6
7
8
Person p = new Person();
Console.WriteLine(p is Person); // True
Console.WriteLine(p is Human); // True
Console.WriteLine(p is Animal); // True

class Animal { }
class Human : Animal { }
class Person : Human { }

as能够实现类型断言(转换),转换不成功则返回null

1
2
object p = new Person();
Person np = p as Person; // 无法转换返回null
null值

可空类型:

1
Nullable<int> x = null;

语法糖:

1
int? x = null;

null值检测语法糖,下面语句,如果x为null,则返回1

1
int y = x ?? 1;
三目运算符
1
2
3
4
int x = 80;
string str = string.Empty;
str = x >= 60 ? "Pass" : "Failed";
Console.WriteLine(str);

8. 表达式语句

反编译

Visual Studio对项目经过编译后,
可以在项目目录/bin/Debug/下找到.exe执行文件,
使用Visual Studio提供的工具: Developer Command Prompt
命令行输入:

1
> ildasm

导入已经被编辑好的exe执行文件,能够反编译出源码:

声明常量
1
const int x = 100;
代码流控制 goto
1
2
hello: Console.WriteLine("Hello, World!");
goto hello;
表达式相关快捷键

快捷键:

  • ctrl + } 实现代码块括号跳转
  • ctrl + l 快速剪切一行
switch

visual studio创建switch语句快捷键:sw

  • case表达式后跟随的数据必须要和switch变量后指定的表达式一致,

  • case表达式后,有执行语句,必须要加break

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
try
{
int Score = Int32.Parse(Console.ReadLine());
switch (Score / 10) {
case 10:
if (Score > 100)
{ // 直接跳转到default
goto default;
}
else {
goto case 8;
}
case 9:
case 8: Console.WriteLine("A");break;
case 7:
case 6: Console.WriteLine("B"); break;
case 5:
case 4: Console.WriteLine("C"); break;
case 3:
case 2:
case 1:
case 0: Console.WriteLine("D"); break;
default:Console.WriteLine("请输入0-100以内的数字");break;
}
}
catch {
Console.WriteLine("请输入数字整数");
}
try/catch-throw/finally
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
class Calculator {
public int Add(string arg1, string arg2) {
int a = 0;
int b = 0;
bool successDone = false;
try
{
a = int.Parse(arg1);
b = int.Parse(arg2);
successDone = true;
}
catch (ArgumentNullException ane)
{
Console.WriteLine(ane.Message);
//Console.WriteLine("Argument cant be null");
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
//Console.WriteLine("Argument must be int");
}
catch (OverflowException oe)
{
Console.WriteLine(oe.Message);
throw oe;
//Console.WriteLine("Overflow");
}
// 无论如何都会执行
finally {
if (successDone)
Console.WriteLine("Success");
else
Console.WriteLine("Failed");
}
int result = checked(a + b);
return result;
}
}
foreach

对于实现IEnumerable接口的类,都能够通过foreach进行循环遍历:

IEnumerable类本身实现了一些用于遍历的方法和属性:

  • MoveNext 指针移动到下一项,如果成功返回true
  • Current 返回当前值
  • Reset 重置当前值

使用IEnumerable实现循环:

1
2
3
4
5
List<int> intList = new List<int>() { 1, 2, 3 };
IEnumerator<int> enumerator = intList.GetEnumerator();
while (enumerator.MoveNext()) {
Console.WriteLine(enumerator.Current);
}

使用foreach进行遍历:

1
2
3
4
List<int> intList = new List<int>() { 1, 2, 3 };
foreach (var current in intList) {
Console.WriteLine(current);
}

9. 字段、属性、索引器、常量

实例字段/静态字段

  • public
  • static
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
static void Main(string[] args)
{
List<TeamMember> MemberList = new List<TeamMember>();
for (int i = 0; i < 10; i++) {
TeamMember Member = new TeamMember("无名氏" + i, "士兵");
}
/**
* 团队名称:作战小队
* 团队成员:10
*/
TeamMember.Intro();
}
class TeamMember {
// 实例字段
public int Health = 100;
// 静态字段
public static int Amount;
// 只读静态字段
public readonly static string TeamName = "作战小队";
// 只读实例字段
public readonly string Progress;
public readonly string Name;
// 构造函数
// 只读实例字段在构造函数里进行初始化
public TeamMember(string name,string progress) {
this.Progress = progress;
this.Name = name;
TeamMember.Amount++;
}
// 静态方法
public static void Intro() {
// 静态方法中访问静态变量
Console.WriteLine("团队名称:{0}", TeamMember.TeamName);
Console.WriteLine("成员数量:{0}", TeamMember.Amount);
}

}

只读字段 readonly

无论是值类型还是引用类型,readonly变量都不能修改:

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
static void Main(string[] args)
{
// 无法进行更改操作:
//Brush.DefaultColor.Green = 0;
Brush.PrintDefaultColor();
Console.ReadKey();
}
struct Color {
public int Red;
public int Green;
public int Blue;
}
class Brush {
public static readonly Color DefaultColor = new Color() {
Red = 0,
Green = 255,
Blue = 255,
};
public static void PrintDefaultColor() {
Console.WriteLine(
"当前笔刷颜色:RGB({0},{1},{2})",
Brush.DefaultColor.Red,
Brush.DefaultColor.Green,
Brush.DefaultColor.Blue
);
}
}

私有字段 private

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
static void Main(string[] args)
{
TreasureBox box = new TreasureBox();
box.SetTreasure("一只狗");
while (true) {
Console.WriteLine("请给出宝箱密语:");
string pass = Console.ReadLine();
try {
string treasure = box.GetTreasure(pass);
Console.WriteLine("密语正确,你获得了【{0}】", treasure);
break;
}catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Console.ReadKey();
}
class TreasureBox
{
// 私有变量,仅能在类内进行修改
private string treasure;
private string pass = "最强武器";
public string GetTreasure(string pass) {
if (pass == this.pass)
{
return this.treasure;
}
else {
throw new Exception("密码错误");
}
}
public void SetTreasure(string treasure) {
this.treasure = treasure;
}
}

get/set 实现属性封装

get/set private

Visual Studio快捷键:

  • propfull + tabtab :快速定义并封装私有变量
  • prop + tabtab : 简略声明私有变量
  • 点击要封装的字段 + ctrl + r + e :重构/封装字段
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
static void Main(string[] args)
{
Cat cat = new Cat();
while (true) {

Console.Write("请选择撸猫的次数:");
string inputNumber = Console.ReadLine();
int number = 0;
try
{
number = Int32.Parse(inputNumber);
cat.PetCount += number;
Console.Write("是否继续(y/n)?");
string input = Console.ReadLine();
if (input == "n") break;
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
}
Console.WriteLine("\n\n================================");
Console.WriteLine("游戏结束");
Console.WriteLine("最终亲密度:{0}", cat.love);
Console.WriteLine("================================");
Console.ReadKey();
}

class Cat {
public int love = 0;
private int petCount;
// 对私有属性的封装,get控制读取,set控制写入
public int PetCount
{
get {
return petCount;
}
set {
love += value * 5;
Console.WriteLine("爱意值:{0}", love);
if (love >= 100) {
Console.WriteLine("你的猫因为感受到太多爱意,即将变身火箭猫");
}
petCount = value;
}
}

}

索引器 Index

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
static void Index()
{
Coffee coffee = new Coffee();
var mocaPrice = coffee["Moca"];
Console.WriteLine(mocaPrice);
coffee["Moca"] = 15;
Console.WriteLine(coffee["Moca"]);
}
class Coffee {
// 键类型:string 值类型:int
private Dictionary<string, int> coffeeDictionary = new Dictionary<string, int>();
// indexer + tabtab
public int? this[string name]
{
get {
/* return the specified index here */
if (this.coffeeDictionary.ContainsKey(name))
{
return this.coffeeDictionary[name];
}
else {
return null;
}
}
set {
if (value.HasValue == false) {
throw new Exception("Price cannot be null");
}
/* set the specified index to value here */
if (this.coffeeDictionary.ContainsKey(name))
{
this.coffeeDictionary[name] = value.Value;
}
else {
this.coffeeDictionary.Add(name, value.Value);
}
}
}
}

10. 参数

  • 传值参数
  • 输出参数
  • 引用参数
  • 数组参数
  • 具名参数
  • 可选参数
  • 扩展方法(this参数)

传值参数

值参数:声明时不带任何修饰符的参数

传值参数:值类型

参数作为传值的副本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void ValueParam_1() {
Player player = new Player();
string words = "我是一个普通的日本高中生";
Player.Say(words); // 主人公:我是一个普通的日本高中生
Console.WriteLine(words); // 我是一个普通的日本高中生
}
class Player {
public readonly static string name = "主人公";
// 值参数:声明不带任何修饰符的参数
// 参数作为传值的副本
public static void Say(string words) {
words = Player.name + ":" + words;
Console.WriteLine(words);
}

}
传值参数:引用类型

直接修改参数引用的内存地址(new操作)时,
函数内对参数的修改不会影响被传入值

GetHashCode: 获取引用类型指向的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void ValueParam_2()
{
// 通过new操作创建出一个新的副本
Progress oldPro = new Progress() { type = "Rapper" };
IntroProgress(oldPro);
Console.WriteLine("{0}:{1}", oldPro.GetHashCode(), oldPro.type);
}
static void IntroProgress(Progress pro) {
pro = new Progress() { type = "Dancer" };
Console.WriteLine("{0}:{1}",pro.GetHashCode() ,pro.type);

}
class Progress {
public string type { get; set; }
}

直接对(引用类型)传值参数的属性进行修改,实际上就是对被传入参数进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void ValueParam_3() {
Enemy enemy = new Enemy() { Status = "被击中" };
UpdateEnemy(enemy);
Console.WriteLine("{0}:{1}",enemy.GetHashCode(), enemy.Status );
}
static void UpdateEnemy(Enemy enemy) {
enemy.Status = "死亡";
Console.WriteLine("{0}:{1}", enemy.GetHashCode(), enemy.Status);
}
class Enemy {
private string status;

public string Status
{
get { return status; }
set { status = value; }
}

}

引用参数 ref

引用类型使用ref进行标识

引用参数:值类型
1
2
3
4
5
6
7
8
9
static void RefererParam_1() {
int x = 10;
IWantSideEffect(ref x);
Console.WriteLine(x);
}
// 使用ref进行标识
static void IWantSideEffect(ref int x) {
x = x + 1;
}
引用参数:引用类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void RefererParam_2() {
Weapen weapen = new Weapen() { name = "小刀" };
Console.WriteLine("HashCode:{0} name:{1}",weapen.GetHashCode(), weapen.name);
Console.WriteLine("--------------------------------");
UpdateWeapen(ref weapen);
Console.WriteLine("HashCode:{0} name:{1}", weapen.GetHashCode(), weapen.name);
}
static void UpdateWeapen(ref Weapen weapen) {
weapen = new Weapen() { name = "钢丝" };
Console.WriteLine("HashCode:{0} name:{1}", weapen.GetHashCode(), weapen.name);
}
class Weapen {
public string name { get; set; }
}

输出形参 out

out和ref效果类似,
不同点在于out可以传入null值,并且必须在函数体中进行赋值

double.TryParse方法使用的就是out传参:

1
2
3
4
5
6
7
8
9
10
static void OutputParam()
{
Console.Write("请输入一个数字(不要轻易尝试数字以外的输入,否则...):");
string input = Console.ReadLine();
double inputValue = 0;
if (double.TryParse(input, out inputValue))
Console.WriteLine("你按照要求输入了数字:{0}", inputValue);
else
Console.WriteLine("你没有按照要求输入,应当受到惩罚");
}

out声明值类型参数:

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
static void TryOut_1 () {
Console.Write("请提供料理食材:");
string material = Console.ReadLine();
string food = "空空如也";
Cooker.CookFood(material,out food);
Console.WriteLine("你获得了:一份{0}", food);
}
class Cooker {
public static void CookFood(string material, out string food) {
if (material != "")
{
if (material.Contains("肉"))
food = "热乎乎的炖肉";
else if (material.Contains("面粉"))
food = "新鲜出炉的面包";
else if (material.Contains("奶油"))
food = "金色奶油蛋糕";
else
food = "美味料理";
}
else {
food = "空气料理";
}
}
}

out声明引用类型

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
static void TryOut_2() {
Console.Write("请为您的巧克力取一个名字:");
string name = Console.ReadLine();
Console.Write("请用一个词形容它的风味:");
string flavor = Console.ReadLine();
Chocolate chocolate = null;
ChocolateFactory(name, flavor, out chocolate);
if (chocolate != null)
{
chocolate.Intro();
}
else {
Console.WriteLine("生产失败,是不是漏了什么?");
}

}
static bool ChocolateFactory(string name, string flavor, out Chocolate chocolate) {
chocolate = null;
if (name != "" && flavor != "") {
chocolate = new Chocolate() { name = name, flavor = flavor};
chocolate.wrapper = "锡纸包装→烫金花纹";
return true;
}
return false;
}
class Chocolate {
public string name { get; set; }
public string flavor { get; set; }
public string wrapper { get; set; }
public void Intro() {
Console.WriteLine("新款巧克力:{0},新口味:{1},新包装:{2}", this.name, this.flavor, this.wrapper);
}
}

数组参数 params

使用数组参数声明,会自动将传参收集为数组格式,
数组参数只能存在一个,并且是被声明的最后一个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void ArrayParam() {
// 不使用数组参数的方法:
//int[] intArray = new int[] { 1, 2, 3, 4, 5 };
//int sum = CalculateSum(intArray);

int sum = CalculateSum(1, 2, 3, 4, 5);
Console.WriteLine(sum);
}
static int CalculateSum(params int[] intArray)
{
int sum = 0;
for (int i = 0; i < intArray.Length; i++) {
sum += intArray[i];
}
return sum;
}

string.Split方法参数声明的方式就是params

1
2
3
4
5
6
7
8
static void UseSplit() {
string str = "空调;冰箱,冰淇淋.大海";
// 此处split参数为数组传参
string[] result = str.Split(';',',','.');
foreach (string word in result) {
Console.WriteLine(word);
}
}

具名参数

一种允许乱序的传参方式:

1
2
3
4
5
6
7
8
static void NameParam() {
LoadGame("存档1",DateTime.Now);
LoadGame(time: DateTime.Now, name:"未命名存档");
}
static void LoadGame(string name, DateTime time)
{
Console.WriteLine("新建存档:{0} 创建时间:{1}", name, time.ToString());
}

可选参数

可选参数需要赋默认值:

1
2
3
4
5
6
7
static void SelectableParam() {
OrderCoffee(orderer:"张三", name:"冰美式");
OrderCoffee();
}
static void OrderCoffee(string name = "招牌拿铁", string orderer = "无名氏") {
Console.WriteLine("您收到了新的美团订单:{0}一份 订餐人:{1}",name, orderer);
}

扩展方法 this

使用this标识参数,能够实现扩展方法

对double类型的扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 3. 必须是静态类
// 4. 静态类命名标准:
// SomeType类 扩展→ SomeTypeExtension类
static class DoubleExtension
{
// 1. 方法必须是public static
// 2. 形参第一个必须是this修饰
public static double Round(this double input, int digits)
{
double result = Math.Round(input, digits);
return result;
}
}
Linq

Language Integrated Query 语言集成查询
Linq内实现了对Enumerable的扩展

1
2
3
4
5
6
static void UseExtension() {
List<int> myList = new List<int> { 11, 12, 13, 14 };
bool res = myList.All(i => i > 10);
bool result = AllGreaterThenTen(myList);
Console.WriteLine(result);
}

11. 委托

Action、Func<>

方法委托就是将方法,用指针进行调用,一般作为参数进行传递:

  • 模板方法

  • 回调方法

  • 委托方法1:Action + Invoke

1
2
3
4
static void Fun(){}

Action action = new Action(Fun)
action.Invoke()
  • 委托方法2:Func<>泛型声明
    1
    2
    3
    4
    5
    6
    7
    8
    static int Add(int x, int y){return x + y;}
    static int Minus(int x, int y){return x - y;}

    Func<int, int, int> delegateAdd = new Func<int, int, int>(Add);
    Func<int, int, int> delegateMinus = new Func<int, int, int>(Add);

    delegateAdd.Invoke(10,20);
    delegateMinus.Invoke(10,20);

delegate声明

delegate类似于声明一个方法签名,
声明一个有参数和返回值特点的模具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void DelegateClass() {
Calculate calculator = new Calculate();

Calc calcAdd = new Calc(calculator.Add);
Calc calcSub = new Calc(calculator.Sub);
Calc calcMul = new Calc(calculator.Mul);
Calc calcDiv = new Calc(calculator.Div);

double x = 10, y = 20;
calcAdd(x, y);
calcSub(x, y);
calcMul(x, y);
calcDiv(x, y);
}
// 委托与封装方法必须类型兼容
public delegate double Calc(double x, double y);
class Calculate {
public double Add(double x, double y) { return x + y; }
public double Sub(double x, double y) { return x - y; }
public double Mul(double x, double y) { return x * y; }
public double Div(double x, double y) { return x / y; }
}

模版方法和回调方法

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
static void TempFunc() {
WrapFactory wrapFact = new WrapFactory();
ProductFactory prodFact = new ProductFactory();
Logger logger = new Logger();
Action<Product> log = new Action<Product>(logger.Log);

Func<Product> ChocoFact = new Func<Product>(prodFact.MakeChocolate);
Func<Product> PizzaFact = new Func<Product>(prodFact.MakePizza);

Box chocoBox = wrapFact.Wrap(ChocoFact, log);
Box pizzaaBox = wrapFact.Wrap(PizzaFact, log);

chocoBox.Open();
pizzaaBox.Open();

}
class Logger
{
public void Log(Product product)
{
Console.WriteLine("产品 {0}| 生产时间 {1} | 价格 {2}", product.Name, DateTime.UtcNow, product.Price);
}
}
class Product {
public string Name { get; set; }
public double Price { get; set; }
}
class Box {
public Product Product { get; set; }
public void Open() {
if (this.Product == null) {
Console.WriteLine("盒子里什么都没有");
return;
}
Console.WriteLine("打开盒子,里面是{0}",this.Product.Name);
}
}
class WrapFactory {
public Box Wrap(Func<Product> GetProduct, Action<Product> logCallback) {
Box box = new Box();

// 模板方法
box.Product = GetProduct.Invoke();

// 回调方法
logCallback(box.Product);

return box;
}
}
class ProductFactory {
public Product MakeChocolate() {
Product product = new Product();
product.Name = "旺卡巧克力";
product.Price = 15;
return product;
}
public Product MakePizza() {
Product product = new Product();
product.Name = "玛格丽特披萨";
product.Price = 50;
return product;
}
}
使用Interface接口代替delegate委托

接口声明与实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface IProductFactory
{
Product Make();
}
class CandyFactory : IProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "糖果";
product.Price = 15.0;
return product;
}
}
class BreadFactory : IProductFactory {
public Product Make() {
Product product = new Product();
product.Name = "巧克力面包";
product.Price = 20.0;
return product;
}
}

修改原本的委托方法参数为接口参数:

1
2
3
4
5
6
7
8
class WrapFactory {
public Box Wrap(IProductFactory prodFact, Action<Product> logCallback) {
Box box = new Box();
box.product = prodFact.Make();
logCallback(box.product);
return box;
}
}

修改方法调用的传参:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void Main(string[] args)
{
WrapFactory wrapFact = new WrapFactory();
IProductFactory candyFact = new CandyFactory();
IProductFactory breadFact = new BreadFactory();
Log log = new Log();

Action<Product> logging = new Action<Product>(log.Logging);

Box candyBox = wrapFact.Wrap(candyFact, logging);
Box breadBox = wrapFact.Wrap(breadFact, logging);

Console.ReadKey();
}

多播委托(multicast)

将多播进行合并,按照合并顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void Multicast() {
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Yellow };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);

action1 += action2;
action1 += action3;

action1.Invoke();
}
class Student {
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework() {
for (int i = 0; i < 5; i++) {
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s)", this.ID, i);
Thread.Sleep(500);
}
}
}

实现异步的方法

实现异步的三种方法:

  • 隐式异步 BeginInvoke
1
2
3
4
5
6
7
Action action1 = new Action(Func1);
Action action2 = new Action(Func2);
Action action3 = new Action(Func3);
// 隐式异步调用
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
  • 线程 Thread
1
2
3
4
5
6
7
Thread thread1 = new Thread(new ThreadStart(Func1));
Thread thread2 = new Thread(new ThreadStart(Func2));
Thread thread3 = new Thread(new ThreadStart(Func3));

thread1.Start();
thread2.Start();
thread3.Start();
  • Task
1
2
3
4
5
6
7
Task task1 = new Task(new Action(Func1));
Task task2 = new Task(new Action(Func2));
Task task3 = new Task(new Action(Func3));

task1.Start();
task2.Start();
task3.Start();

12. 事件

事件基本概念

事件的触发与响应

事件的本质是假装在委托字段上的一个蒙版

事件模型组成部分:

  • 事件拥有者 eventSource
  • 事件成员 event
  • 事件响应者 eventSubscriber
  • 事件处理器 eventHandler
  • 事件订阅

挂接事件处理器使用的是语法糖:

1
eventSource.evnet += eventSubscriber.eventSubscriber;

触发Timer.Elapsed事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void UseEvent() {
System.Timers.Timer timer = new System.Timers.Timer();
timer.Interval = 1000;
Boy boy = new Boy();
Girl girl = new Girl();
timer.Elapsed += boy.Action;
timer.Elapsed += girl.Action;
timer.Start();
}
class Boy
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump");
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Sing");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void UseEvent() {
ExtendForm form = new ExtendForm();
form.ShowDialog();
}
class ExtendForm : Form {
private TextBox textBox;
private Button button;
public ExtendForm() {
this.textBox = new TextBox();
this.button = new Button();
this.button.Text = "Click Me";
this.button.Top = 150;
this.Controls.Add(this.button);
this.Controls.Add(this.textBox);

this.button.Click += this.ButtonClicked;
}

private void ButtonClicked(object sender, EventArgs e)
{
this.textBox.Text = "Hello, World!";
}
}
事件绑定的几种方式

被绑定事件的参数类型和返回值如下:

1
private void ButtonClick(object sender, EventArgs e){}
  1. 语法糖
1
this.button.Click += this.ButtonClick
  1. EventHandler
1
this.button.Click += new EventHandler(this.ButtonClick)
  1. delegate 匿名函数委托
1
2
3
this.button.Click += delegate (object sender, EventArgs e){
this.textBox1.Text = "Button Clicked";
}
  1. lambda表达式
1
2
3
this.button,Click += (sender, e) => {
// event detail
}

实现整体事件流程

下面是一个点餐的事件模型:

  • 事件拥有者:Customer客户
  • 事件成员:OrderEventArgs点餐
  • 事件响应者:Waiter服务员
  • 事件处理器:Action送餐
  • 事件订阅:为客户的点餐事件指派一个服务员并进行响应

定义点餐事件委托,以及事件参数:

1
2
3
4
5
6
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class OrderEventArgs : EventArgs
{
public string DishName { get; set; } // 订餐菜名
public string Size { get; set; } // 订餐分量
}

定义客户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Customer
{
public double Bill { get; set; }
// 支付账单
public void PayBill()
{
Console.WriteLine("Pay the bill:{0}", this.Bill);
}
// 走进
public void WalkIn() {
Console.WriteLine("Walk In,,,");
}
// 找位置坐下
public void SitDown() {
Console.WriteLine("Sit Down...");
}
}

为Customer类声明事件类型属性:

1
2
3
4
5
6
7
8
9
10
public class Customer
{
// 声明事件
private OrderEventHandler orderEventHandler;
public event OrderEventHandler Order
{
add { this.orderEventHandler += value; }
remove { this.orderEventHandler -= value; }
}
}

简化写法:

1
2
3
4
public class Customer
{
public event OrderEventHandler Order;
}

定义服务员以及订餐响应方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("Got Order: {0}", e.DishName);
double price = 10;
switch (e.Size) {
case "small":
price *= 0.5;
break;
case "big":
price *= 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}

为客户定义事件触发方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Customer{
public void Thinking() {
for (int i = 0; i < 3; i++) {
Console.WriteLine("Thinking...");
Thread.Sleep(1000);
}

if (this.orderEventHandler != null) {
OrderEventArgs e = new OrderEventArgs();
e.DishName = "French Eggs";
e.Size = "small";
this.orderEventHandler.Invoke(this, e); // 触发事件
}
}
public void Action() {
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Thinking();
}
}

通过事件触发和响应串通整个流程:

1
2
3
4
5
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;
customer.Action();
customer.PayBill();

13. 类

析构函数 destructor

析构函数和construct相对应,在实例被销毁时执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Chocolate {
// ctor 创建构造器快捷键
public string Name { get; set; }
public double Price { get; set; }
public Chocolate(string Name, double Price)
{
this.Name = Name;
this.Price = Price;
}
// 析构函数
~Chocolate() {
Console.WriteLine("Chocolate {0} be destroyed", this.Name);
}
public void Report() {
Console.WriteLine(this.Name);
}
}

反射

通过class的类型创建objet或dynamic变量承接
object实现反射:

1
2
3
4
Type t = typeof(Chocolate);
object o = Activator.CreateInstance(t,"咖啡夹心巧克力",25.5);
// 不能直接调用Chocolate上的实例,需要进行类型转换:
Chocolate choco = o as Chocolate;

dynamic反射

1
2
3
Type t = typeof(Chocolate);
// 动态类型可以直接调用
dynamic o = Activator.CreateInstance(t,"咖啡夹心巧克力",25.5);

静态构造函数

VSCode构造函数快捷键:ctor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Pie
{
public static int Amount { get; set; }
// 在类声明时就执行
static Pie()
{
Amount = 100;
}
public Pie()
{
Amount += 1;
Console.WriteLine("桌子上有派端上来,现在是{0}个派", Amount);
}
~Pie() {
Amount -= 1;
Console.WriteLine("桌子上的派被拿走了,剩下{0}个派", Amount);
}
}

类的声明

关键字
  • new
  • public
  • protected
  • internal 内部类,只有同一装配集内成员能够访问
  • private
  • abstract
  • sealed 密封类不可作为父类
  • static
Assembly

Tip: Ctrl + - 跳转到上次光标快捷键

每个项目的编译结果就是Assembly装配集,
Assembly主要分为2类:

  • exe 可执行文件
  • dll 类库

类继承

C#内所有类的基类都是Object

1
2
3
Type t = typeof(Candy);
Type tb = t.BaseType;
Console.WriteLIne(tb.FullName); // System.Object

带有sealed关键字声明的类无法被继承,

1
public sealed NoChildClass{}
  • c#中只能继承自一个基类,但能实现多个基接口
  • 子类的访问权限不能超过父类
类扩展

类继承就是对父类进行横向和纵向的扩展:

  • 横向: 类属性和方法的增加
  • 纵向:对父类内属性和方法的重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Chocolate : Candy
{
public Chocolate(string subMaterial)
{
this.mainMaterial = "coco" + subMaterial;
}
}
class MilkChocolate : Chocolate {
// 表示调用父类构造函数时传递参数
// 构造器时不能被继承的
public MilkChocolate():base("milk")
{
this.mainMaterial = "coco + milk";
}
public void ShowParentMat() {
Console.WriteLine (base.mainMaterial);
}
}
类继承和类成员访问级别关系

重写&多态

重写标识符:

  • 父类 virtual
  • 子类 override

隐藏和重写的区别:

  • 方法/属性不加重写标识,算作子类对父类的隐藏
  • 隐藏下父子类之间没有版本关系

方法和属性的重写:

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
// 父类
class Candy{
private string flavor;
public virtual string Flavor{
get { return flavor; }
set { flavor = value; }
}
// virtual标识符
public virtual void Cook(){
Console.WriteLine("Cook Candy");
}
}
// 子类
class Chocolate: Candy{
private string flavor;
public override string Flavor{
get { return flavor; }
set { flavor = "巧克力+" + value; }
}
// override标识符
public override void Cook(){
Console.WriteLine("Cook Chocolate");
}
}


14.接口

抽象类&接口

  • 抽象类
    • 函数成员并未完全实现的类
    • 虚方法成员必须是public标识
    • 不能实例化,需要靠派生类来实现抽象方法
    • 纯虚方法声明关键字:abstract
    • 子方法实现关键字:override
  • 接口
    • 功能等同于纯抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 接口
interface IFood{
void Cook();
void Eat();
}
// 抽象类
abstract class Candy: IFood{
public void Eat(){
Console.WriteLine("美味!")
}
public abstract void Cook();
}
class Chocolate: Candy{
public override void Cook(){
Console.WriteLine("Cooking Chocolate...")
}
}

松耦合

松耦合

使用接口,而不是具体类来作为另一个类的成员的类型,
接口类成员使用该类的实现类赋值
减轻两个类之间的耦合关系

接口实现:

1
2
3
public interface ICoffin{
double HasCoffin();
}

接口实现类:

1
2
3
4
5
public class Coffee: ICoffin{
public double HasCoffin(){
return 30.0;
}
}

主动耦合类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Custom{
ICoffin _coffee;
public Custom(ICoffin coffee){
_coffee = coffee;
}
public string Drink(){
double coffin = _coffee.HasCoffin();
string mes = "";
if(coffin <= 0) {mes = "无咖啡因"}
else if(coffin<=30) {mes = "低咖啡因"}
else if(coffin<=60) {mes = "适量咖啡因"}
else if(coffin<=100) {mes = "咖啡高浓度因"}
else{ mes = “致死量咖啡因” }
}
}

主动耦合类的调用:

1
2
Custom custom = new Ciustom(new Coffee());
custom.Drink();

单元测试

解决方案 → 添加新项目 → xUnit测试

松耦合的2种测试方式:

  • 创建类
  • Moq

方式一:创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CoffeeTests{
[Fact]
public void NoneCoffInCoffee_OK(){
var custom = new Custom(new NoneCoffinCoffee());
var actual = custom.Drink();
var expected = "无咖啡因添加";
Assert.Equal(expected, actual);
}
class NoneCoffinCoffee: InterfaceLearning.ICoffin{
public double HasCoffin(){
return 0;
}
}
}

方式二:Moq
用NuGet搜索下载引入Moq

1
2
3
4
5
6
7
8
9
10
11
public class CoffeeTests{
[Fact]
public void NoneCoffInCoffee_OK(){
var mock = new Mock<ICoffin>();
mock.Setup(coffin => coffin.HasCoffin()).Returns(()=>0);
var custom = new Custom(mock.Object);
var actual = custom.Drink();
var expected = "无咖啡因添加";
Assert.Equal(expected, actual);
}
}