本文主要介绍:
一:由c基础过度到c#,所需的基础知识板块。
二:由介绍的c#_板块,基于unity开发游戏的功能实践与练习。
框架
一、C 到 C# 过渡的基础知识
- 四大类型金刚
- 基础类型
- 委托类型
- 类的类型与多态
- 接口
- 其他知识点
- Lambda 表达式
- 属性
- 泛型
- List<T> 详解
- 初始化与容量管理
- 元素操作
- 查询与查找
- 排序与转换
- 事件
二、待补充的 C# 知识点
-
异步编程(async/await)
- 游戏应用:资源加载(如
AssetBundle.LoadAsync
)、网络请求、长时间运算(如寻路)。 - 替代方案:Unity 的协程(
IEnumerator
+yield return
),但 async/await 语法更简洁。
- 游戏应用:资源加载(如
-
扩展方法
- 游戏应用:为 Unity 内置类型(如
Transform
、GameObject
)添加自定义工具方法,减少重复代码。
- 游戏应用:为 Unity 内置类型(如
-
异常处理(try-catch-finally)
- 游戏应用:防止因外部资源加载失败(如配置文件损坏)导致游戏崩溃。
-
LINQ 查询
- 游戏应用:快速筛选游戏对象集合(如查找所有敌人、最近的道具)。
-
任务并行库(TPL)
- 游戏应用:后台线程处理非 UI 逻辑(如数据计算、AI 决策),避免阻塞主线程。
-
内存管理(GC)
- 游戏应用:优化频繁创建的对象(如子弹、特效),减少 GC 卡顿(使用对象池)。
-
反射与特性
- 游戏应用:自动绑定 UI 组件(通过特性标记)、插件系统(动态加载程序集)。
一:
基础类型四金刚:
基础类型:
就和c里面一样,int,double,float,char,bool等....还有var(隐式类型,根据右边自动转换)。
委托类型:
先看举例:
【第一步】
delegate void DeliveryCallback(string address); // 定义委托类型
/*
其他定义方法:
// 无参数
Action sayHello = () => Console.WriteLine("Hi");
// 单参数可省略括号
Func<int, int> square = x => x * x;
// 多参数必须用括号
Func<int, int, int> add = (a, b) => a + b;
*/
class 奶茶店 {
【第二步】
public DeliveryCallback 外卖小哥; // 委托实例
public void 订单来了() {
外卖小哥?.Invoke("北京市海淀区"); // 调用委托链,也可以:外卖小哥("北京市海淀区");
}
}
class 美团骑手 {
public void 开始送餐(string addr) {
print($"美团骑手出发去{addr}");
}
}
class 饿了么骑手 {
public void 配送流程(string addr) {
print($"饿了么骑手已接单,目标:{addr}");
}
}
var 店铺 = new 奶茶店();
var 美团 = new 美团骑手();
var 饿了么 = new 饿了么骑手();
【第三步】
// 登记多个外卖员(+= 是委托链的关键操作)
店铺.外卖小哥 += 美团.开始送餐;
店铺.外卖小哥 += 饿了么.配送流程;//如果最后饿了么用的是=而非+=,就会只保留最后=的,也就是饿了么,美团OUT。
【跑起来】
// 触发事件
店铺.订单来了();
// 输出:
// 美团骑手出发去北京市海淀区
// 饿了么骑手已接单,目标:北京市海淀区
本质是实现一个“在一定情况下,去调用某或某些满足基本格式的方法“的一个功能。具体实现为:【第一步】,
相当于定义了一个公告板(可以用构建无名方法的关键字delegate,也可以用NET框架里面的委托泛型Action无返回值,Func有返回值的方法定义),并且防止传参与返回错误,简单定义了一下参数和返回值这两个东西,基本还是属于白板。
【第二步】,
相当于需要公告板的class类,去实例化一下公告板,并且去类里面自己设定逻辑,什么逻辑下去调用啊,就是实现调用某或某些满足格式的方法的行为,使用委托实例.Invoke(参数)。
【第三步】,
将满足委托公告板要求的那些需要被调用的某或某些方法给“+=”或者直接=(单情况)去实现委托链的构建。
类的类型:
先看举例:
// 父类
class Animal
{
public virtual void Speak()
{
Console.WriteLine("动物发出声音");
}
}
// 子类
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("狗汪汪叫");
}
}
// 子类
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("猫喵喵叫");
}
}
// 使用多态
class Program
{
static void Main()
{
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.Speak(); // 输出:狗汪汪叫
animal2.Speak(); // 输出:猫喵喵叫
}
}
// 抽象类
abstract class Shape
{
public abstract double Area();
}
// 子类
class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
// 子类
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
return Width * Height;
}
}
// 使用多态
class Program
{
static void Main()
{
Shape shape1 = new Circle { Radius = 5 };
Shape shape2 = new Rectangle { Width = 4, Height = 6 };
Console.WriteLine($"圆形面积: {shape1.Area()}"); // 输出:圆形面积: 78.53981633974483
Console.WriteLine($"矩形面积: {shape2.Area()}"); // 输出:矩形面积: 24
}
}
// 接口
interface IPrintable
{
void Print();
}
// 类实现接口
class Book : IPrintable
{
public string Title { get; set; }
public void Print()
{
Console.WriteLine($"打印书籍: {Title}");
}
}
// 类实现接口
class Document : IPrintable
{
public string Content { get; set; }
public void Print()
{
Console.WriteLine($"打印文档: {Content}");
}
}
// 使用多态
class Program
{
static void Main()
{
IPrintable printable1 = new Book { Title = "C#编程指南" };
IPrintable printable2 = new Document { Content = "重要文件内容" };
printable1.Print(); // 输出:打印书籍: C#编程指南
printable2.Print(); // 输出:打印文档: 重要文件内容
}
}
介绍:
就是面向对象编程,将一个功能需要的方法和变量,这边c#里面还有属性,封装在类calss里面,用修饰符修饰这个访问权限。,一个一个类即功能块之间呢又可以通过父子继承,来实现子类对父类的复用、异化、扩充、升级。然后通过:“virtual虚方法,override重写虚方法”、“abstract 抽象类,抽象方法,等待不同子类实现”、“insert接口与实现”。来实现一个模版,多个阐述的多态性。(c#是单继承,多接口),补充:sealed
关键字修饰的类称为 “密封类”,其核心作用是禁止被其他类继承。
修饰符 | 说明 |
---|---|
public | 公开的,任何地方都能访问。 |
private | 私有的,只有类内部能访问(默认修饰符)。 |
protected | 受保护的,类内部和子类能访问。 |
internal | 内部的,同一程序集(Assembly)内可访问(常用于项目内部分享)。 |
protected internal | 受保护的内部成员,同一程序集或子类可访问。 |
多态体现通道 | 说明 |
---|---|
virtual 虚方法 | 虚方法不一定要被重写,只是提供一个override重写方法, 体现多态的通道。 |
abstract 抽象类 | 抽象类里面可以有普通方法或抽象方法 (抽象方法必须在抽象类里面,抽象类里面不一定要抽象方法) 抽象方法只是声明,不实现,是契约。等待子类继承者【必须重写】 |
insert 接口 | 一个类可以实现多个接口,接口里面只声明,不实习,全是契约。 等待实现接口的类直接实现,不用重写关键字。并且不是强制契约。 |
接口类型:
只有些注意事项,因为第三个类型已经基本介绍了。
当两个接口有同名方法时,需用显式接口实现指定具体实现哪个接口的方法:
C# 8.0 允许接口为方法提供默认实现,实现类可以选择是否重写(类似虚方法)。
二、其他
lamade表达式
额lamade表达式在c#里面说白了就是,取代delegate关键字的一种不命名创建函数的方法,用(参数)=>{}来代替函数声明。
// 传统委托
delegate int Calculate(int a, int b);
Calculate add = delegate(int a, int b) { return a + b; };
// Lambda 表达式(无需命名,直接定义函数体)
Calculate addLambda = (a, b) => a + b;
属性
属性public string Name { get; set; } = "Unnamed";比如这个,其实成员本身就有set,get但是是没有任何限制的,但是我们可以通过对get,set进行设置,来筛选更改和获取。
泛型:
看代码:
// 普通类:只能存储 int
class IntList {
private int[] items;
public void Add(int item) { /* ... */ }
public int Get(int index) { return items[index]; }
}
// 泛型类:可以存储任意类型(T 是占位符)
class GenericList<T> {
private T[] items;
public void Add(T item) { /* ... */ }
public T Get(int index) { return items[index]; }
}
// 使用泛型类
var intList = new GenericList<int>(); // 指定 T 为 int
intList.Add(10);
int value = intList.Get(0); // 返回 int
var stringList = new GenericList<string>(); // 指定 T 为 string
stringList.Add("hello");
string text = stringList.Get(0); // 返回 string
说白了,在想用泛型类,接口,方法前,在类,接口,方法的名后面加<T>,这个里面就可以用T来占位了
对标STL_C++:
这个内容太大,我会单开一篇幅总结,关注蹲后续
C# 泛型类 | 类似 C++ STL | 说明 |
---|---|---|
List<T> | vector<T> | 动态数组,自动扩容,支持随机访问。 |
Dictionary<TKey, TValue> | map<TKey, TValue> | 基于哈希表的键值对集合,查找速度 O (1)。 |
LinkedList<T> | list<T> | 双向链表,支持 O (1) 插入 / 删除。 |
Queue<T> | queue<T> | 先进先出(FIFO)队列。 |
Stack<T> | stack<T> | 后进先出(LIFO)栈。 |
HashSet<T> | unordered_set<T> | 不重复元素的集合,基于哈希表。 |
SortedSet<T> | set<T> | 排序的不重复元素集合,基于红黑树。 |
SortedDictionary<TKey, TValue> | map<TKey, TValue> | 排序的键值对集合,基于红黑树。 |
简单聊一下第一个List<T>:
一、List<T> 的核心方法与属性
List<T>
位于 System.Collections.Generic 命名空间,提供了动态数组的功能,无需手动管理内存。以下是常用方法和属性:
1. 初始化与容量管理
方法 / 属性 | 说明 |
---|---|
List<T>() | 无参构造函数,初始容量为 0,添加元素时自动扩容。 |
List<T>(int capacity) | 指定初始容量,减少扩容次数,提高性能。 |
Capacity | 获取或设置当前容量(元素数量超过容量时自动翻倍)。 |
TrimExcess() | 将容量调整为当前元素数量,减少内存占用。 |
2. 元素操作
方法 / 属性 | 说明 |
---|---|
Add(T item) | 在列表末尾添加一个元素。 |
AddRange(IEnumerable<T> collection) | 添加多个元素(如另一个列表)。 |
Insert(int index, T item) | 在指定位置插入元素,后续元素后移。 |
Remove(T item) | 删除第一个匹配的元素,返回是否删除成功。 |
RemoveAt(int index) | 删除指定位置的元素。 |
RemoveRange(int index, int count) | 删除指定范围的元素。 |
Clear() | 清空所有元素,容量不变。 |
[index] | 通过索引访问或修改元素(如 list[0] = value )。 |
3. 查询与查找
方法 / 属性 | 说明 |
---|---|
Count | 获取当前元素数量(只读)。 |
Contains(T item) | 判断列表是否包含指定元素(使用 Equals 比较)。 |
IndexOf(T item) | 返回第一个匹配元素的索引,不存在则返回 -1。 |
LastIndexOf(T item) | 返回最后一个匹配元素的索引。 |
Find(Predicate<T> match) | 返回第一个满足条件的元素,不存在则返回 default(T) 。 |
FindAll(Predicate<T> match) | 返回所有满足条件的元素组成的新列表。 |
Exists(Predicate<T> match) | 判断是否存在满足条件的元素。 |
4. 排序与转换
方法 / 属性 | 说明 |
---|---|
Sort() | 对列表元素进行排序(默认按元素的 IComparable<T> 实现)。 |
Sort(Comparison<T> comparison) | 使用自定义比较器排序。 |
Reverse() | 反转列表元素的顺序。 |
ToArray() | 将列表转换为数组。 |
ConvertAll<TOutput>(Converter<T, TOutput> converter) | 将列表元素转换为另一种类型。 |
using System;
using System.Collections.Generic;
class ListMethodDemo
{
static void Main()
{
#region 1. 初始化与容量管理
// 无参构造(初始容量 0,自动扩容)
List<int> numbers = new List<int>();
Console.WriteLine("1. 初始化后容量: " + numbers.Capacity); // 输出: 0
// 带初始容量构造(减少扩容次数)
List<string> names = new List<string>(5); // 初始容量 5
Console.WriteLine("1. 带容量初始化后容量: " + names.Capacity); // 输出: 5
// 手动设置容量(不会改变元素数量)
numbers.Capacity = 10;
Console.WriteLine("1. 手动设置容量后容量: " + numbers.Capacity); // 输出: 10
// 裁剪容量(仅当当前容量 > 125% 元素数量时生效)
numbers.TrimExcess(); // 此时元素数量为 0,容量可能变为 0
#endregion
#region 2. 元素操作(增、插、删)
// 2.1 添加单个元素
numbers.Add(3);
numbers.Add(1);
numbers.Add(4);
Console.WriteLine("\n2.1 Add 后元素: " + string.Join(", ", numbers)); // 输出: 3, 1, 4
// 2.2 添加多个元素(AddRange)
numbers.AddRange(new int[] { 1, 5, 9 });
Console.WriteLine("2.2 AddRange 后元素: " + string.Join(", ", numbers)); // 输出: 3, 1, 4, 1, 5, 9
// 2.3 在指定位置插入元素(Insert)
numbers.Insert(2, 100); // 在索引 2 插入 100
Console.WriteLine("2.3 Insert 后元素: " + string.Join(", ", numbers)); // 输出: 3, 1, 100, 4, 1, 5, 9
// 2.4 删除第一个匹配元素(Remove)
bool removed = numbers.Remove(1); // 删除第一个 1
Console.WriteLine($"2.4 Remove 成功: {removed}, 剩余元素: " + string.Join(", ", numbers)); // 输出: 成功, 3, 100, 4, 1, 5, 9
// 2.5 删除指定索引元素(RemoveAt)
numbers.RemoveAt(1); // 删除索引 1 的 100
Console.WriteLine("2.5 RemoveAt 后元素: " + string.Join(", ", numbers)); // 输出: 3, 4, 1, 5, 9
// 2.6 删除指定范围元素(RemoveRange)
numbers.RemoveRange(1, 2); // 从索引 1 开始删除 2 个元素(4, 1)
Console.WriteLine("2.6 RemoveRange 后元素: " + string.Join(", ", numbers)); // 输出: 3, 5, 9
// 2.7 清空所有元素(Clear)
// numbers.Clear(); // 测试时可取消注释
#endregion
#region 3. 查询与查找
// 3.1 获取元素数量(Count)
int count = numbers.Count;
Console.WriteLine("\n3.1 元素数量: " + count); // 输出: 3
// 3.2 判断是否包含元素(Contains)
bool has3 = numbers.Contains(3);
Console.WriteLine("3.2 是否包含 3: " + has3); // 输出: True
// 3.3 查找元素索引(IndexOf)
int indexOf5 = numbers.IndexOf(5);
Console.WriteLine("3.3 元素 5 的索引: " + indexOf5); // 输出: 1
// 3.4 查找最后一个匹配元素的索引(LastIndexOf)
numbers.Add(5); // 现在列表: 3, 5, 9, 5
int lastIndexOf5 = numbers.LastIndexOf(5);
Console.WriteLine("3.4 最后一个 5 的索引: " + lastIndexOf5); // 输出: 3
// 3.5 查找第一个满足条件的元素(Find)
int firstLarge = numbers.Find(n => n > 5); // 找第一个 >5 的数
Console.WriteLine("3.5 第一个大于 5 的元素: " + firstLarge); // 输出: 9
// 3.6 查找所有满足条件的元素(FindAll)
List<int> allLarge = numbers.FindAll(n => n > 5);
Console.WriteLine("3.6 所有大于 5 的元素: " + string.Join(", ", allLarge)); // 输出: 9, 5(注意 5 不大于 5,实际应为 9)
// 3.7 判断是否存在满足条件的元素(Exists)
bool hasLarge = numbers.Exists(n => n > 10);
Console.WriteLine("3.7 是否存在大于 10 的元素: " + hasLarge); // 输出: False
#endregion
#region 4. 排序与转换(重点:自定义排序)
// 4.1 默认排序(升序,依赖元素的 IComparable 实现)
numbers.Sort();
Console.WriteLine("\n4.1 默认排序后: " + string.Join(", ", numbers)); // 输出: 3, 5, 5, 9
// 4.2 自定义排序(降序,使用 Comparison<T> 委托)
// 方式 1:直接传入 lambda 表达式(推荐)
numbers.Sort((a, b) => b.CompareTo(a)); // 降序:b - a
Console.WriteLine("4.2 自定义降序排序后: " + string.Join(", ", numbers)); // 输出: 9, 5, 5, 3
// 方式 2:通过自定义方法(适合复杂逻辑)
numbers.Sort(MyCustomComparison); // 调用自定义比较方法
Console.WriteLine("4.2 自定义升序排序后: " + string.Join(", ", numbers)); // 输出: 3, 5, 5, 9
// 4.3 反转元素顺序(Reverse)
numbers.Reverse();
Console.WriteLine("4.3 反转后: " + string.Join(", ", numbers)); // 输出: 9, 5, 5, 3
// 4.4 转换为数组(ToArray)
int[] numberArray = numbers.ToArray();
Console.WriteLine("4.4 转换为数组: " + string.Join(", ", numberArray)); // 输出: 9, 5, 5, 3
// 4.5 转换元素类型(ConvertAll)
List<string> stringNumbers = numbers.ConvertAll(n => $"数字: {n}");
Console.WriteLine("4.5 转换为字符串列表: " + string.Join(", ", stringNumbers)); // 输出: 数字: 9, 数字: 5, 数字: 5, 数字: 3
#endregion
}
// 自定义比较方法(实现升序排序)
static int MyCustomComparison(int a, int b)
{
// 升序逻辑:a - b
// 降序逻辑:b - a
return a.CompareTo(b); // 等价于 a - b(int 实现了 IComparable)
}
}
事件:
事件是被保护的委托,是安全性更高,防止误触与恶意攻击的保护写法:
public class Publisher {
// 事件(带保护)
public event Action<string> OnEvent;
// 委托变量(无保护)
public Action<string> OnAction;
public void TriggerEvent() {
// 发布者内部触发事件(合法)
OnEvent?.Invoke("事件被发布者触发");
}
public void TriggerAction() {
// 发布者内部触发委托(合法)
OnAction?.Invoke("委托被发布者触发");
}
}
public class Subscriber {
public void Test(Publisher pub) {
// 尝试触发事件(编译错误!)
// pub.OnEvent?.Invoke("外部触发事件"); ❌ 事件无法直接调用
// 尝试覆盖事件(编译错误!)
// pub.OnEvent = s => Console.WriteLine("覆盖事件"); ❌ 事件禁止赋值
// 尝试触发委托(合法,但危险!)
pub.OnAction("外部触发委托"); ✅ 委托可被外部调用
// 尝试覆盖委托(合法,但危险!)
pub.OnAction = s => Console.WriteLine("覆盖委托"); ✅ 委托可被外部赋值
}
}
特性 | 事件(event) | 委托变量(非事件) |
---|---|---|
调用权限 | 仅声明类内部可调用 | 外部可直接调用(public 时) |
订阅 / 取消订阅 | 仅支持 += /-= | 支持 += /-= /= (覆盖) |
外部覆盖订阅者 | 禁止(编译错误) | 允许(= 直接赋值) |
空引用异常 | 自动处理(?.Invoke() ) | 需手动判空(因为可以直接调用) |
设计目的 | 安全的发布 - 订阅模式 | 通用方法引用 |
二:
关于c#->unity,我下一篇再发。然后要补充的c#的知识点也下一篇发,前面的足够基础入门去写小游戏了。