C#高级编程5.0 读书笔记

写在前面

发现一个买实体书的好处吧,当你发现屋子里的乱七八糟的东西越来越多,想清理掉的时候,发现一堆没怎么翻过的书在那里,就只能把它看完,做好笔记再扔了。
这本书真的是比砖头还要厚,里面的各种知识点都很详细。但因为一些东西太过基础,一些东西又不是我想做的方向,所以我就对着目录圈出了自己觉得需要看的地方。下面做一个笔记,算是填充自己的资料库吧。
&& 发现里面有些东西有点旧了,但还是现看完再说,新添的特性再额外补充一下。

语法方面

  1. 关于两个main()函数:一般C#程序中,只允许有一个静态main函数作为入口,否则会编译错误。(看目录的时候还以为多个main函数可以有什么特殊操作特殊用法,结果并没有
  2. 闭包:声明匿名函数的时候,如果引用了匿名函数内部引用了外部的量,就会形成一个闭包。关于闭包,我个人理解就是匿名函数在实际执行的时候,会隐式创建一个类,这个类包含构造方法和一个与匿名函数相同的public方法,外部变量通过构造方法传到隐式类里面来,调用匿名函数时执行隐式类的public方法。了解了大致的原理,我们就可以知道,首先写程序的时候应该避免频繁使用带闭包的匿名函数;其次注意匿名函数中[[引用]]的外部变量,每次调用匿名函数,外部变量的值都是最新值,比如for循环中Dotween的OnComplete中输出循环变量 i 值,输出的 i 值不是从0开始,而是循环最大值这个高版本语言已经升级改掉了,不用在意这条,但低版本语言上还是有这个特性的,仍然需要注意
  3. 逆变和协变:协变out,逆变in。用在接口上,可以控制传参和返回值的类型大小。这地方确实比较绕。下面有个简单例子,帮助理解。其中A大于B表示A包含B,能用B的地方一定能用A。(一定要看注释)(个人觉得这个东西自然而然的就用出来了,死扣定义没什么大用,用多了就好了。&&写错了属于语法错误,有报错的,也不用怕出稀奇古怪的bug)
        /// 1. string 是 object 的子类 ==》 string < object
        /// 2. 作为输入值,能输入object的地方一定能输入string 
        ///   ==》 action 包含 action 
        ///   ==》 action 大于 action   
        ///    ==》 大小关系发生变化,为逆变
        /// 3. 作为输出值,能输出object的地方一定能输出string
        ///   ==> func 包含  func
        ///   ==> func 小于 func 
        ///   ==> 大小关系不变,为协变
        /// 4. 上面的2,3只是简单的举例,实际使用时,可以自己控制in,out参数
        
        Action v = null;
        Action c = v;

        Func s = null;
        Func d = ()=>
        {
            return s();
        };
 
 
  1. dynamic类型,可以在运行时确定变量的类型,而不是在编译阶段。但有两个限制,一个是动态类型不支持扩展方法,另一个是动态类型不能作为匿名函数的参数。个人觉得,dynamic在反射之类的东西里面会比较有用,平时一般开发中,基本不会用上这个东西。&&C#本身并不是动态语言,为了实现这个动态语言的功能,C#内部做了很多很多的操作,这也就导致了一个问题,他的效率并不高。

字符串

  1. string类
    需要注意的是,string类方法的一系列操作,基本都是重新创建新字符串,进行一系列处理后再将旧字符串丢弃,新字符串返回。所以,一些长字符串操作不要用string,影响效率。另外,除了常用的字符串方法,比如format之类的,还有其他的字符串方法,可以简单实现我们的需求。遇到类似的问题要先记得查一下API,尽量不要直接自己上手写,一是非学习情况下造轮子没什么意义,二是自己造的轮子还不一定好用。

  2. 长字符串的处理:使用StringBuilder
    StringBuilder仅能够替换追加删除字符串中的文本,而且它的效率很高。
    StringBuilder在能够设置Capacity,也就是操作字符串的空间大小。所有对字符串的操作都在这个空间中进行,而不用额外去申请内存空间,从而提高操作字符串的效率。在默认不设置Capacity的情况下,StringBuilder创建的空间会比初始化所用的字符串大,&&如果操作的字符超出所设置的Capacity,一般会自动翻倍。
    值得额外注意⚠️的是,只有对字符串频繁进行操作,才能获得StringBuilder的性能优势。

  3. 关于Fromat
    需要额外知道的就是通过实现IFormattable接口,可以自定义format格式。下面是一个简单的例子:

public class A : IFormattable
{
    public string content;
   
    public string ToString(string format, IFormatProvider formatProvider)
    {
        switch (format)
        {
            case "UP":
                return content.ToUpper();
                break;
            case "LOW":
                return content.ToLower();
                break;
            default:
                return content;
                break;
        }    
    }
}
var a = new A();
a.content = "Hello World !!!";

print($"{a:LOW}");
print(a.ToString("UP", null));
  1. 正则表达式
    关键字 Regex
    常用的正则表达式关键字.png

内存管理和指针

  1. 堆和栈
    1. 栈:存储值类型数据
    2. 托管堆:存储引用类型数据
  2. C#中的指针
    1. 在C#中使用指针需要添加unsafe标记
    2. 在类型后面添加*表示对应类型的指针。eg. int* byte*[]
    3. &取地址操作;*获取地址内容操作
  3. sizeof确定各种数据类型所占的空间大小(字节数)
  4. stackalloc分配一定大小的内存。下面是一个高速数组的例子:(需要注意的是,这种数据是没有越界报错的,需要自己额外检查)
unsafe static void Test()
{
    int* intArray = stackalloc int[10];
    for (int i = 0; i < 10; i++)
    {
        intArray[i] = i;
    }
    
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(*(intArray + i));
    }
}

反射

  1. 这个东西游戏开发代码中一般是用不到的。其他的.net具体是什么样子就不清楚了,没有做过.net的正式商业项目,只是自己写过demo工具之类。欢迎大佬告知。
  2. 其实就是用名字来找对应的类型方法属性等等,然后对其进行动态创建调用方法等操作。
  3. 个人的使用感受:根据名字=》找程序集=》找类型=》找方法属性字段等等。找到你想找的量进行操作就好了。
    但这个名字有时候容易写不对。就Unity来说,Unity自己的非编辑器C#脚本是打在一个dll里面的,写编辑器工具时,不好直接填那个dll的名字。我当时是找了一个非编辑器类,用这个类取它所在的程序集,然后用获取到的这个程序集进行反射,来找其他的想要的类。算是一个偷懒的办法吧。

异步

  1. 最最基本的,使用Thread实现。这个没什么太需要注意的。需要注意一下的就是启动新线程消耗会比较大,要避免频繁创建线程。&&C#提供了其他的异步类和接口,这个可以基本不用了。
  2. Task,下面是几种创建&&启动方式。类&接口知道了,其他的细节需要的时候直接查一下就好了,这里不再记录。
创建

Task.Factory.StartNew

Task.Run

Task task1 = new Task(() =>
{
    Console.WriteLine(123);
});
task1.Start();
阻塞等待,当然还有其他的接口,用的时候直接看补全提示就好了
Task.WaitAll(task1)
  1. CancellationTokenSource 取消常驻线程辅助类。
    它的主要作用就是标记量,但相比普通的boolint,使用方便&&有自带的触发回掉接口。
  2. async await,用法感觉上和协程的yield return差不多。
    class Program
    {
        static void Main(string[] args)
        {
            var content = Test1().Result;//这个属性是阻塞的
            Console.WriteLine(content);
        }

        static async Task Test1()//这里返回值还可以是`Task` `void`
        {
            string path = "";
            FileStream fs = new FileStream(path, FileMode.Open);
            StreamReader sr = new StreamReader(fs);

            string content = await sr.ReadToEndAsync();//这里等待读取完成

            return content;
        }
    }
  1. 并行Parallel:注意并行不保证执行顺序
    Parallel有三个静态方法,分别是Invoke,For,Foreach
    1. Parallel.Invoke(params Action[] actions):能够同时执行参数中的方法。
    2. Parallel.For:多次执行一个方法。如果需要执行的次数大于当前任务线程的数量,则同时执行的方法数量为当前最大任务线程数,这一批次执行完毕再执行下一个批。ParallelLoopState对象的Break方法能够提前终止该次并行。
    Parallel.For(0, 40, (i, p) =>
    {
        string content = $"i:{i} taskId:{Task.CurrentId} ThreadId:    {Thread.CurrentThread.ManagedThreadId}";
        Console.WriteLine(content);
    
        Thread.Sleep(1000);
    });
    
    3.Parallel.Foreach:并行执行一个方法。第二个Action参数中,第一个为迭代的值,第二个是并行状态变量,第三个为迭代次数。
    string[] test = new[]
    {
        "Hello",
        "World",
        "HHH",
        "123",
        "456",
    };
    
    Parallel.ForEach(test, (a, p, i) =>
    {
        Console.WriteLine($"content:{a}, long:{i}");
    });
    
  2. System.Timer.Timer 计时器,延迟多长时间触发事件。
  3. 关于线程间数据同步,第一个原则就是能不同步就不同步。必须同步的,除了简单的使用lock,还有其他的Spinlock,Interlocked等(++i不是线程安全的)。

一个实用工具

使用ildasm工具进行代码分析(反编译)。


断断续续,看一点写一点拖了好久,终于吧想看的看完了。

最后感谢大神写的好文章
深入理解 C# 协变和逆变,给了我很大启发。
C# Task和async/await详解,介绍的很详细。

你可能感兴趣的:(C#高级编程5.0 读书笔记)