感觉还是得再学习下C#
C# .net源码
core源码
开始!
C#学习知识点
调试Debug
直接在VSCode里设置断点在Unity里不生效
找到一个帖子使用VSCode进行Unity开发也不行
似乎很麻烦,VSCode也不怎么支持了,先这样吧
构成C#语言的基本元素
- 关键字 add as if 。。。。。。
- 操作符 + = - 。。。。。。。
- 标识符 起的名 规则如下
标识符必须以字母、下划线或 @ 开头,后面可以跟一系列的字母、数字( 0 - 9 )、下划线( _ )、@。
标识符中的第一个字符不能是数字。 标识符必须不包含任何嵌入的空格或符号,比如 ? - +! # % ^ & * ( ) [ ] { } .
; : " ’ / \。
标识符不能是 C# 关键字。除非它们有一个 @ 前缀。 例如,@if 是有效的标识符,但 if 不是,因为 if是关键字。
标识符必须区分大小写。大写字母和小写字母被认为是不同的字母。
不能与C#的类库名称相同。
- 标点符号 ; {} ()
- 文本(字面值)等号右侧的东西
整数 int long
实数 float double
字符 char
字符串 string
布尔 bool
空 null - 注释与空白 // /**/ ///
运算符优先级
类型
懒得写了,直接看吧C#数据类型
敲重点:动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的
属性
声明快捷方式:prop
属性可以在访问时实时计算出属性值,而字段是需要主动去进行计算
根据使用场景来判断使用哪个
建议:永远使用属性(而不是字段)来暴露数据
// 声明类型为 int 的 Age 属性
private int age;
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
索引器
声明快捷方式:indexer
参数传递
- 值参数
就是正常的参数传递 - 引用参数
public void swap(ref int x, ref int y){}
参数传递的是使用传进来变量的内存位置获取值,所以参数改变原变量也会变
- 输出参数
在引用参数的基础上,传入的参数可以不初始化赋值
public void getValues(out int x, out int y ){
Console.WriteLine("请输入第一个值: ");
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入第二个值: ");
y = Convert.ToInt32(Console.ReadLine());
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a , b;
/* 调用函数来获取值 */
n.getValues(out a, out b);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.WriteLine("在方法调用之后,b 的值: {0}", b);
Console.ReadLine();
}
数组参数
不确定参数个数时可以这样用
public int AddElements(params int[] arr){}
// 调用
app.AddElements(512, 720, 250, 567, 889);
// 或者
app.AddElements(new int[]{512, 720, 250, 567, 889});
可空类型
float? num1; // 默认值为null 可以表示float范围内值再加一个null
num1 = num2 ?? 6.66; // num2 如果为空值则返回 6.66
数组
这块有点懵逼
二维数组
可以理解为一个矩阵表格,元素长度要一致
int[,] a = new int[5, 2] {{0,0}, {1,2}, {2,4}, {3,6}, {4,8} };
int i, j;
/* 输出数组中每个元素的值 */
for (i = 0; i < 5; i++)
{
for (j = 0; j < 2; j++)
{
Console.WriteLine("a[{0},{1}] = {2}", i, j, a[i,j]);
}
}
交错数组
数组的数组
每个元素长度可以不同
交错数组为一维数组
int[][] a = new int[][]{new int[]{0,0},new int[]{1,2},
new int[]{2,4},new int[]{ 3, 6 }, new int[]{ 4, 8 , 6} };
int i, j;
/* 输出数组中每个元素的值 */
for (i = 0; i < 5; i++)
{
for (j = 0; j < 2; j++)
{
Console.WriteLine("a[{0}][{1}] = {2}", i, j, a[i][j]);
}
}
密封类
不能被继承的类
sealed class Person {}
抽象类
- 抽象类不能被实例化
- 不能用sealed修饰符修饰抽象类
- 抽象类方法不能是静态的
- 抽象方法不能标记为virtual
- 抽象类可以有构造方法,但构造方法不能直接被调用而只能由派生类的构造方法调用
// 抽象方法
abstrace class Person {
public abstract void Speak();
}
// 派生方法
class Student: Person {
public override void Speak(){
Console.WriteLine("我是学生");
}
}
接口
- 接口中只能声明方法、属性、索引器和事件
返回值类型 方法名(参数);
- 接口不能声明字段、构造方法、常量和委托
- 一个类既可以派生自另一个类还可以同时实现某个接口
- 一个类可以同时实现多个接口
class PrintHP:Print, IPrint, IScan {}
接口作为方法的参数
// 前情提要:接口IPrint由两个类实现PrintHp和PrintIBM
IPrint iHP = new PrintHP();
IPrint iIBM = new PrintIBM();
PrintPhoto(iHP);
// 接口作为参数
public static void PrintPhoto(IPrint i) {
// 可以根据传入的不同对象进行不同的打印方法,实现了多态
i.Print();
}
接口的绑定
接口继承接口
可以把不同接口合在一起称为一个新的接口
interface ISumSport: ISport, ILalaDui {}
As和Is
当不确定一个对象是否为某一个类型时
- is
测试一个实例是不是另一个类型 - as
测试一个实例是不是另一个类型,并且把实例转换为另一种类型,如果转换不成功则返回null
抽象类和接口
接口 | 抽象类 | |
相同点 | ||
都不能被实例化 | ||
包含有未实现的方法 | ||
子类必须实现所有未实现的方法 | ||
不同点 | ||
interface关键字 | abstrace关键字 | |
子类可以实现多个接口 | 子类只能继承一个抽象类 | |
直接实现方法 | 使用override关键字实现 |
多态
虚方法
父类中需要子类去实现的方法
// 虚方法
public virtual int area()
{
Console.WriteLine("父类的面积:");
return 0;
}
// 子类中重写虚方法
public override int area ()
{
Console.WriteLine("Rectangle 类的面积:");
return (width * height);
}
虚方法和抽象方法
虚方法 | 抽象方法 |
---|---|
用virtual修饰 | 用abstract修饰 |
要有方法体 | 不允许有方法体 |
可以被子类override | 必须被子类override |
除了密封类都可以写 | 只能在抽象类中 |
委托
- 委托是一个类
- 主要作用是将方法作为方法的参数
疑问?还是不太理解有什么作用,我直接调用AddCompute方法也可以
/// <summary>
/// 用委托实现一个计算器功能
/// </summary>
/// <returns></returns>
//定义委托
public delegate double Calculator(double num1, double num2);
class Program
{
static void Main(string[] args)
{
//实例化委托
Calculator cal = new Calculator(AddCompute);
// 使用委托
double res = cal(2.4, 34.5); // 36.5
}
// 定义方法 (一般是先定义方法再定义委托 委托的参数和方法参数必须保持一致)
public static double AddCompute(double num1, double num2)
{
return num1 + num2;
}
}
匿名方法
- 实例化委托时直接将方法体放在后面
- 不需要指定匿名方法的返回值类型
修改委托的例子
// 匿名方法(实例化委托+定义方法)
Calculator cal = delegate (double num1,double num2) {
return num1 + num2;
};
事件
用户双击了鼠标,点击了关闭按钮,滚动滚轮。。。。。。都是事件
类似之前学习的订阅发布者模式,事件源不需要知道谁来响应自己发布任务就好,订阅者不需要知道什么时候触发事件知道怎么响应就好,实例中的主函数就是事件控制中心
- 可以把事件看成委托的容器
- 事件要素 (以学校类上课事件举例)
– 事件源(老师)
– 订阅者 (学生)
– 订阅者处理事件 (学生回到座位安静坐好)
– 要素间的关系 (老师说上课了,学生去上课)
/// <summary>
/// 上课事件
/// </summary>
//定义委托 (习惯在事件委托后加Handler)
public delegate void TeachHandler();
// 事件源
class Teacher
{
// 定义事件
// [访问修饰符] event 委托名 事件名;
public event TeachHandler TeachEvent;
// 定义方法
public void BeginClass()
{
Console.WriteLine("老师:上课啦!!!!!!!");
// 处理事件
if (TeachEvent != null)
{
// 执行事件
TeachEvent();
}
}
}
// 订阅者
class Student
{
string name;
public string Name { get; set; }
public Student(string name)
{
this.name = name;
}
// 定义方法
public void SleepListener()
{
Console.WriteLine("{0}在课堂上睡觉zzzzzz", name);
}
public void GoodListener()
{
Console.WriteLine("{0}在课堂上认真听课", name);
}
public void VeryGoodListener()
{
Console.WriteLine("{0}在课堂上一边认真听课一边记笔记", name);
}
}
class Program
{
static void Main(string[] args)
{
Teacher t = new Teacher();
Student s1 = new Student("张三");
Student s2 = new Student("李四");
Student s3 = new Student("王五");
// 订阅事件
// 事件名 += new 委托(事件处理方法名)
t.TeachEvent += new TeachHandler(s1.SleepListener);
t.TeachEvent += new TeachHandler(s2.VeryGoodListener);
t.TeachEvent += new TeachHandler(s3.GoodListener);
// 触发事件
t.BeginClass();
/* Console
老师:上课啦!!!!!!!
张三在课堂上睡觉zzzzzz
李四在课堂上一边认真听课一边记笔记
王五在课堂上认真听课
*/
}
}
泛型集合
- List<T> 对应 ArrayList
- Dictionary<T> 对应 HashTable
- 明确指定放入集合中对象的数据类型
- 优点
– 类型安全,编译期间泛型集合会检查放入的对象数据类型
– 提高性能,存取数据时不会发生类型转换,特别是存取值类型时不会发生装箱和拆箱操作
List
class Study
{
static void Main(string[] args)
{
// 泛型集合
// List<数据类型> 实例名 = new List<数据类型>()
List<Student> studentList = new List<Student>();
Student s1 = new Student("张三", 1);
Student s2 = new Student("李四", 2);
Student s3 = new Student("王五", 3);
Student s4 = new Student("赵六", 4);
studentList.Add(s1);
studentList.Add(s2);
studentList.Add(s3);
studentList.Add(s4);
//展示所有学生
studentList.ForEach(delegate (Student s)
{
Console.WriteLine("学号{0},姓名{1}", s.Name, s.Id);
});
// 获取所有学号大于2的学生
List<Student> newStuList = studentList.FindAll(delegate (Student s)
{
return s.Id > 2;
});
}
}
class Student
{
public string Name { get; set; }
public int Id { get; set; }
public Student()
{
}
public Student(string name, int id)
{
this.Name = name;
this.Id = Id;
}
}
Dictionary
// Dictionary<键数据类型,值数据类型> 实例名 = new Directionary<键数据类型,值数据类型>()
Dictionary<string, int> propValueDir = new Dictionary<string, int>
{
{ "生命值", 100 },
{ "法力值", 194},
{ "心情值", 10}
};
propValueDir.Add("防御值", 4000);
Console.WriteLine(propValueDir.Count); // 4
foreach (var propName in propValueDir.Keys)
{
Console.WriteLine("{0}:{1}", propName, propValueDir[propName]);
}
/*
生命值:100
法力值:194
心情值:10
防御值:4000
*/
对象和集合的初始化器
- 对象初始化器 不再需要构造函数去赋值
class Study
{
static void Main(string[] args)
{
// 对象初始化器
// 实例化学生对象
Student s1 = new Student{ Id = 1, Name = "刘六"};
}
}
class Student
{
public string Name { get; set; }
public int Id { get; set; }
}
- 集合初始化器
好巧,就是上节课代码提示给的
// 初始化器
Student s1 = new Student("张三", 1);
Student s2 = new Student("李四", 2);
Student s3 = new Student("王五", 3);
Student s4 = new Student("赵六", 4);
List<Student> studentList = new List<Student>{
s1,s2,s3,s4
};
Dictionary<string, int> propValueDir = new Dictionary<string, int>
{
{ "生命值", 100 },
{ "法力值", 194},
{ "心情值", 10}
};
Lambda表达式
- 是一个匿名函数
// 数数组里有多少奇数
int[] numbers = { 23, 525, 66, 32, 65, 88, 456, 83, 36 };
int count = numbers.Count((n) =>
{
return n % 2 == 1;
});
// 或者 简写
count = numbers.Count(n => n % 2 == 1);
Console.WriteLine("数组中奇数个数为:{0}", count);
预处理器
以#开头的一行命令,在编译之前进行处理
似乎是用于调试或者版本控制之类的
和使用正常控制的区别是可以避免某些代码进行编译
菜鸟教程
异常
- 请勿将try/catch处于控制流
- 用户只能处理catch异常
- 不得声明空catch块
- 避免在catch块内嵌套try/catch
- 只有使用finally块才能从try语句中释放资源
try catch finally
一般先catch特殊异常,最后放Exception
try {
// 程序代码
} catch (异常类型 参数名) {
// 异常处理
} catch (另一个异常类型 参数名) {
// 异常处理
} finally {
// 清楚内存等
}
throw
抛出异常 可以是系统异常也可以是自定义异常
class HelloWorld
{
static void Main(string[] args)
{
try
{
int divisor = 0;
if (divisor == 0)
{
throw new MyException("除数为0?");
}
}
catch (MyException ex)
{
Console.WriteLine("出错啦!错误信息:" + ex.Message);
}
}
}
// 自定义异常
class MyException : ApplicationException
{
// 异常信息继承于父类异常信息
public MyException(string message) : base(message) { }
}
小知识
- 前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,没有前缀则表示十进制。
int i; //默认值0
int? ii; //默认值null
- 类的默认修饰符号是internal,接口的默认修饰符号是public
- var弱类型 类似object 性能比object高点
using System.Threading;
Thread.Sleep(2000); // 进程暂停两秒
- 按F12可以查看定义代码
递归
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public int factorial(int num)
{
/* 局部变量定义 */
int result;
if (num == 1)
{
return 1;
}
else
{
result = factorial(num - 1) * num;
return result;
}
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
//调用 factorial 方法
Console.WriteLine("6 的阶乘是: {0}", n.factorial(6));
Console.WriteLine("7 的阶乘是: {0}", n.factorial(7));
Console.WriteLine("8 的阶乘是: {0}", n.factorial(8));
Console.ReadLine();
}
}
}
修饰符
- public 全部都可访问
- internal 同一程序集可以访问
- protected 子类可以访问
- private 只有自己可以访问
- sealed 标识类不能被继承或方法不能被重写