C#学习日记

发布于:2025-07-11 ⋅ 阅读:(13) ⋅ 点赞:(0)

委托

一句话总结委托

委托(delegate)就是一种可以引用函数的类型,是C#实现回调和事件机制的核心。
你可以把它理解为“函数指针的安全封装”

一、委托的基础结构
 

public delegate void MyDelegate(string msg); // 声明一个委托类型

void SayHello(string name)
{
    Console.WriteLine("Hello, " + name);
}

MyDelegate d = SayHello; // 委托绑定方法
d("基德大人");            // 调用委托(本质上就是调用 SayHello)

二、委托VS方法的关系
 

委托 方法
类型 变量/引用
可以绑定多个方法 自身只是一段可执行的逻辑
支持异步/广播机制 不具备这些特性
本质是对象,可以传递 方法不能独立传递

三、委托的常见类型

匿名方法(更灵活)
 
MyDelegate d = delegate(string name) {
    Console.WriteLine("你好," + name);
};


Lambda表达式(最常用)
 
MyDelegate d = (name) => Console.WriteLine("欢迎你," + name);
多播委托(绑定多个方法)

 
MyDelegate d = MethodA;
d += MethodB;
d("基德大人"); // 依次调用 MethodA 和 MethodB

四、委托在游戏开发中的用途(超多)

场景 用法
✅ UI 事件响应 点击按钮时绑定委托回调
✅ 消息/事件广播 EventMgr.Inst.Broadcast(delegate)
✅ 动画完成回调 动画播放结束时执行某函数
✅ 异步加载 资源加载完成后调用绑定的方法
✅ 状态切换 用委托表示“当前状态要执行的行为”

五、.NET中的内置委托类型
 
委托类型 定义 示例
Action 无返回值的方法 Action a = DoSomething;
Action<T> 带一个参数的无返回值方法 Action<int> a = ShowHp;
Func<T> 有返回值的方法 Func<int> f = GetScore;
Func<T,R> 参数和返回值都有的方法 Func<int, string> f = ToString;
Predicate<T> 返回 bool 的委托(常用于查找) Predicate<int> p = x => x > 0;

六、委托和事件的关系

委托是基础,事件是对委托的“安全封装”,防止外部直接调用

public event MyDelegate OnClosePage; // 只能 += 或 -=,不能直接调用


eg:EventMgr.Inst.Broadcast(IntEvt.Fetch(EventId.OpenUI, ...));
底层一定是使用委托链表(多播委托)来广播事件的。



七、底层机制简析
 
  • 委托在 IL 中是一个类,它内部封装了:

    • 方法指针

    • 实例对象(闭包)

  • 多播委托是一个链表结构

  • 每次 += 会新建一个新的委托链节点

  • 调用时是链式执行(同步)


委托是函数的变量,绑定回调最灵便;事件加锁更安全,游戏通信少不了。”

协程

协程是一种可以暂停执行、然后在之后恢复的函数机制。

它不是线程!但能实现“异步延时、不阻塞主线程”的效果。


为什么游戏开发需要协程?

举个例子:

  • 玩家点击按钮打开 UI 页面

  • 系统需要先加载资源,再显示动画,再播放音效…

这就涉及一连串动作,但你不希望写成一大堆嵌套回调。

✅ 协程就能让你像写同步代码一样完成异步操作!


Unity中协程的基本语法
 

IEnumerator LoadAndShow()
{
    Debug.Log("开始加载...");
    yield return new WaitForSeconds(2); // 延迟 2 秒
    Debug.Log("加载完成,显示UI");
}

void Start()
{
    StartCoroutine(LoadAndShow()); // 启动协程
}


协程的核心结构:IE numerator + yield return

关键词 含义
IEnumerator 返回一个可枚举器,允许函数被“暂停”
yield return 暂停协程,等待一个条件或下一帧
StartCoroutine 启动协程(通常写在 MonoBehaviour 里)

常用的yield return 类型

语句 说明
yield return null 下一帧继续
yield return new WaitForSeconds(2) 等待指定秒数
yield return new WaitUntil(() => 条件) 等待某条件成立
yield return StartCoroutine(另一个协程) 等待另一个协程执行完毕

 协程与普通方法地区别

特点 普通方法 协程方法
是否可中断执行 ❌ 否 ✅ 是
执行时间 一次性执行完毕 可以分帧分时执行
异步效果 ❌ 无 ✅ 有
场景用途 逻辑/数学 等待、动画、加载、UI等


协程是如何实现的?

协程底层是C#的 IEnumerator + 状态机原理构建的。
编译器会把你的 yield return 转换成一个状态机。
 
switch (state)
{
    case 0: ... state = 1; return WaitForSeconds;
    case 1: ... state = 2; return null;
}
所以协程本质是一种“分布执行”的状态逻辑管理器。

实战例子:UI页面渐隐显示
 
IEnumerator FadeIn(CanvasGroup cg)
{
    float t = 0;
    while (t < 1)
    {
        t += Time.deltaTime; // 每帧加时间
        cg.alpha = t;        // 改变透明度
        yield return null;   // 等下一帧
    }
}

Unity和协程相关的补充说明:
  • 协程依赖 MonoBehaviour,不能在普通类中直接使用。

  • 可以用 StopCoroutine() 来手动停止协程。

  • 协程不能像线程那样并行执行多个逻辑,它们还是运行在主线程,只是**“按帧分步执行”**。



协程VS异步 async/await

特性 协程 Coroutine async/await
语法结构 yield return await
使用范围 Unity 特有 C# 全局支持
可等待对象 WaitForSeconds、null等 Task、异步函数等
是否真正异步 ❌ 不是(主线程上) ✅ 是多线程异步

在 Unity 中,协程是更自然的异步方式;但在非 Unity 的 C# 程序中,推荐使用 async/await

“协程写异步,代码变清晰;非阻塞不卡顿,游戏体验强。”

反射

特性

#define Fun
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

知识点一 特性是什么
    特性是一种允许我们向程序的程序集添加元数据的语言结构
    它是用于保存程序结构信息的某种特殊类型的类

    特性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。
    特性与程序实体关联后,即可在运行时使用反射查询特性信息

    特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中
    它可以放置在几乎所有的声明中(类、变量、函数等等申明)

    说人话:
    特性本质是个类
    我们可以利用特性类为元数据添加额外信息
    比如一个类、成员变量、成员方法等等为他们添加更多的额外信息
    之后可以通过反射来获取这些额外信息
 


知识点二 自定义特性
    继承特性基类 Attribute

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
    class MyCustomAttribute : Attribute
    {
        //特性中的成员 一般根据需求来写
        public string info;

        public MyCustomAttribute(string info)
        {
            this.info = info;
        }

        public void TestFun()
        {
            Console.WriteLine("特性的方法");
        }
    }


 

知识点三 特性的使用
    基本语法:
    [特性名(参数列表)]
    本质上 就是在调用特性类的构造函数
    写在哪里?
    类、函数、变量上一行,表示他们具有该特性信息

    [MyCustom("这个是我自己写的一个用于计算的类")]
    [MyCustom("这个是我自己写的一个用于计算的类")]
    class MyClass
    {
        [MyCustom("这是一个成员变量")]
        public int value;

        //[MyCustom("这是一个用于计算加法的函数")]
        //public void TestFun( [MyCustom("函数参数")]int a )
        //{

        //}
        public void TestFun(int a)
        {

        }
    }

知识点四 限制自定义特性的使用范围


    通过为特性类 加特性 限制其使用范围
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
    参数一:AttributeTargets —— 特性能够用在哪些地方
    参数二:AllowMultiple —— 是否允许多个特性实例用在同一个目标上
    参数三:Inherited —— 特性是否能被派生类和重写成员继承

    public class MyCustom2Attribute : Attribute
    {

    }
 

知识点五 系统自带特性——过时特性
    过时特性
    Obsolete

    用于提示用户 使用的方法等成员已经过时 建议使用新方法
    一般加在函数前的特性

    class TestClass
    {
        参数一:调用过时方法时 提示的内容
        参数二:true-使用该方法时会报错  false-使用该方法时直接警告

        [Obsolete("OldSpeak方法已经过时了,请使用Speak方法", false)]
        public void OldSpeak(string str)
        {
            Console.WriteLine(str);
        }

        public void Speak()
        {

        }

        public void SpeakCaller(string str, [CallerFilePath]string fileName = "", 
            [CallerLineNumber]int line = 0, [CallerMemberName]string target = "")
        {
            Console.WriteLine(str);
            Console.WriteLine(fileName);
            Console.WriteLine(line);
            Console.WriteLine(target);
        }
    }

知识点六 系统自带特性——调用者信息特性
    哪个文件调用?
    CallerFilePath特性
    哪一行调用?
    CallerLineNumber特性
    哪个函数调用?
    CallerMemberName特性

    需要引用命名空间 using System.Runtime.CompilerServices;
    一般作为函数参数的特性
 

知识点七 系统自带特性——条件编译特性
    条件编译特性
    Conditional

    它会和预处理指令 #define配合使用

    需要引用命名空间using System.Diagnostics;
    主要可以用在一些调试代码上
    有时想执行有时不想执行的代码
 

知识点八 系统自带特性——外部Dll包函数特性
    DllImport    用来标记非.Net(C#)的函数表明该函数在一个外部的DLL中定义。
    一般用来调用 C或者C++的Dll包写好的方法
    需要引用命名空间 using System.Runtime.InteropServices
 

    class Program
    {
        [DllImport("Test.dll")]
        public static extern int Add(int a, int b);

        [Conditional("Fun")]
        static void Fun()
        {
            Console.WriteLine("Fun执行");
        }

        static void Main(string[] args)
        {
            Console.WriteLine("特性");

            #region 特性的使用
            MyClass mc = new MyClass();
            Type t = mc.GetType();
            //t = typeof(MyClass);
            //t = Type.GetType("Lesson21_特性.MyClass");

            //判断是否使用了某个特性
            //参数一:特性的类型
            //参数二:代表是否搜索继承链(属性和事件忽略此参数)
            if( t.IsDefined(typeof(MyCustomAttribute), false) )
            {
                Console.WriteLine("该类型应用了MyCustom特性");
            }

            //获取Type元数据中的所有特性
            object[] array = t.GetCustomAttributes(true);
            for (int i = 0; i < array.Length; i++)
            {
                if( array[i] is MyCustomAttribute )
                {
                    Console.WriteLine((array[i] as MyCustomAttribute).info);
                    (array[i] as MyCustomAttribute).TestFun();
                }
            }

            TestClass tc = new TestClass();
            tc.OldSpeak("123");
            tc.Speak();

            tc.SpeakCaller("123123123123123");

            Fun();
            #endregion
        }


   

    总结:


    特性是用于 为元数据再添加更多的额外信息(变量、方法等等)
    我们可以通过反射获取这些额外的数据 来进行一些特殊的处理
    自定义特性——继承Attribute类

     系统自带特性:过时特性

     为什么要学习特性
     Unity引擎中很多地方都用到了特性来进行一些特殊处理

 

迭代器

知识点一 迭代器是什么
    迭代器(iterator)有时又称光标(cursor)
    是程序设计的软件设计模式
    迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素
    而又不暴露其内部的标识

    在表现效果上看
    是可以在容器对象(例如链表或数组)上遍历访问的接口
    设计人员无需关心容器对象的内存分配的实现细节
    可以用foreach遍历的类,都是实现了迭代器的
 

知识点二 标准迭代器的实现方法
    关键接口:IEnumerator,IEnumerable
    命名空间:using System.Collections;
    可以通过同时继承IEnumerableIEnumerator实现其中的方法

    class CustomList : IEnumerable, IEnumerator
    {
        private int[] list;
        //从-1开始的光标 用于表示 数据得到了哪个位置
        private int position = -1;

        public CustomList()
        {
            list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        }

        #region IEnumerable
        public IEnumerator GetEnumerator()
        {
            Reset();
            return this;
        }
        #endregion

        public object Current
        {
            get
            {
                return list[position];
            }
        }
        public bool MoveNext()
        {
            //移动光标
            ++position;
            //是否溢出 溢出就不合法
            return position < list.Length;
        }

        //reset是重置光标位置 一般写在获取 IEnumerator对象这个函数中
        //用于第一次重置光标位置
        public void Reset()
        {
            position = -1;
        }
    }
    #endregion

知识点三 yield return 语法糖实现迭代器
    yield return 是C#提供给我们的语法糖
    所谓语法糖,也称糖衣语法
    主要作用就是将复杂逻辑简单化,可以增加程序的可读性
    从而减少程序代码出错的机会

    关键接口:IEnumerable
    命名空间:using System.Collections;
    让想要通过foreach遍历的自定义类实现接口中的方法GetEnumerator即可   

class CustomList2 : IEnumerable
    {
        private int[] list;

        public CustomList2()
        {
            list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        }

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < list.Length; i++)
            {
                yield关键字 配合迭代器使用
                可以理解为 暂时返回 保留当前的状态
                一会还会在回来
                C#的语法糖
                yield return list[i];
            }
            yield return list[0];
            yield return list[1];
            yield return list[2];
            yield return list[3];
            yield return list[4];
            yield return list[5];
            yield return list[6];
            yield return list[7];
        }
    }


知识点四 yield return 语法糖为泛型类实现迭代器

    class CustomList<T> : IEnumerable
    {
        private T[] array;

        public CustomList(params T[] array)
        {
            this.array = array;
        }

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < array.Length; i++)
            {
                yield return array[i];
            }
        }
    }
    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("迭代器");

            CustomList list = new CustomList();

            foreach本质 
            1.先获取in后面这个对象的 IEnumerator
              会调用对象其中的GetEnumerator方法 来获取
            2.执行得到这个IEnumerator对象中的 MoveNext方法
            3.只要MoveNext方法的返回值时true 就会去得到Current
              然后复制给 item

            foreach (int item in list)
            {
                Console.WriteLine(item);
            }

            foreach (int item in list)
            {
                Console.WriteLine(item);
            }

            CustomList<string> list2 = new CustomList<string>("123","321","333","555");
            foreach (string item in list2)
            {
                Console.WriteLine(item);
            }
            foreach (string item in list2)
            {
                Console.WriteLine(item);
            }

        }
    }
}

总结:
迭代器就是可以让我们在外部直接通过foreach遍历对象中元素而不需要了解其结构
主要的两种方式
1.传统方式 继承两个接口 实现里面的方法
2.用语法糖 yield return 去返回内容 只需要继承一个接口即可

 

特殊语法

using System;
using System.Collections.Generic;

namespace Lesson23_特殊语法
{
    class Person
    {
        private int money;
        public bool sex;

        public string Name
        {
            get => "怪盗基德";
            set => sex = true;
        }

        public int Age
        {
            get;
            set;
        }

        public Person(int money)
        {
            this.money = money;
        }

        public int Add(int x, int y) => x + y;

        public void Speak(string str) => Console.WriteLine(str);
    }
 class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("特殊语法");

   

知识点一 var隐式类型
            var是一种特殊的变量类型
            它可以用来表示任意类型的变量
            注意:
            1.var不能作为类的成员 只能用于临时变量申明时
              也就是 一般写在函数语句块中
            2.var必须初始化
   
            var i = 5;
            var s = "123";
            var array = new int[] { 1, 2, 3, 4 };
            var list = new List<int>();
    

知识点二 设置对象初始值
            申明对象时 
            可以通过直接写大括号的形式初始化公共成员变量和属性
            Person p = new Person(100) { sex = true, Age = 18, Name = "唐老狮" };
            Person p2 = new Person(200) { Age = 18 };
 

知识点三 设置集合初始值
            申明集合对象时
            也可以通过大括号 直接初始化内部属性

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

            List<int> listInt = new List<int>() { 1, 2, 3, 4, 5, 6 };

            List<Person> listPerson = new List<Person>() {
                new Person(200),
                new Person(100){Age = 10},
                new Person(1){sex = true, Name = "唐老狮"}
            };

            Dictionary<int, string> dic = new Dictionary<int, string>()
            {
                { 1, "123" },
                { 2, "222"}
            };

知识点四 匿名类型
            var 变量可以申明为自定义的匿名类型
            var v = new { age = 10, money = 11, name = "小明" };
            Console.WriteLine(v.age);
            Console.WriteLine(v.name);
 

知识点五 可空类型
            1.值类型是不能赋值为 空的
            int c = null;
            2.申明时 在值类型后面加? 可以赋值为空
            int? c = 3;
            3.判断是否为空
            if( c.HasValue )
            {
                Console.WriteLine(c);
                Console.WriteLine(c.Value);
            }
            4.安全获取可空类型值
            int? value = null;
              4-1.如果为空 默认返回值类型的默认值
            Console.WriteLine(value.GetValueOrDefault());
              4-2.也可以指定一个默认值
            Console.WriteLine(value.GetValueOrDefault(100));

            float? f = null;
            double? d = null;

            
            object o = null;
            if( o != null )
            {
                Console.WriteLine(o.ToString());
            }
            相当于是一种语法糖 能够帮助我们自动去判断o是否为空
            如果是null就不会执行tostring也不会报错

            Console.WriteLine(o?.ToString());

            int[] arrryInt = null;

            Console.WriteLine(arrryInt?[0]);

            Action action = null;
            if (action != null)
            {
                action();
            }
            action?.Invoke();

知识点六 空合并操作符
             空合并操作符 ??
            左边值 ?? 右边值
          如果左边值为null 就返回右边值 否则返回左边值
            只要是可以为null的类型都能用

            int? intV = null;
           int intI = intV == null ? 100 : intV.Value;
            int intI = intV ?? 100;
            Console.WriteLine(intI);

            string str = null;
            str = str ?? "hahah";
            Console.WriteLine(str);
 

知识点七 内插字符串
            关键符号:$
            用$来构造字符串,让字符串中可以拼接变量
           

string name = "怪盗基德";
            int age = 18;
            Console.WriteLine($"好好学习,{name},年龄:{age}");
            #endregion

知识点八 单句逻辑简略写法
            当循环或者if语句中只有 一句代码时 大括号可以省略
         

  if (true)
                Console.WriteLine("123123");

            for (int j = 0; j < 10; j++)
                Console.WriteLine(j);

            while (true)
                Console.WriteLine("123123");

            #endregion


 


网站公告

今日签到

点亮在社区的每一天
去签到