Technology Csharp - chaolunner/CloudNotes GitHub Wiki

C#中值类型和引用类型的区别

  • 速度上的区别: 值类型存取速度快,引用类型存取速度慢。
  • 用途上的区别: 值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用。
  • 来源上的区别: 值类型继承自System.ValueType,引用类型继承自System.Object。
  • 类型上的区别: 值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。
  • 内存释放上的区别: 栈的内存分配是自动释放;而堆在.NET中会有GC来释放。
  • 保存位置上的区别: 值类型变量直接把变量的值保存在堆栈中,引用类型的变量把实际数据的地址保存在堆栈中,而实际数据则保存在堆中。

注意,堆和堆栈是两个不同的概念,在内存中的存储位置也不相同,堆一般用于存储可变长度的数据,如字符串类型。而堆栈则用于存储固定长度的数据,如整型类型的数据int(每个int变量占用四个字节)。

C#的new关键字的几种用法

  • new 运算符:用于创建对象和调用构造函数。

  • new 修饰符:在用作修饰符时,new 关键字可以显式隐藏从基类继承的成员。

    new 修饰符会用同样的名称创建一个新成员并使原始成员变为隐藏的。
    override 修饰符会扩展继承成员的实现。
    
  • new 约束:用于在泛型声明中约束可能用作类型参数的参数的类型。

    void Main()
    {
        // 此处编译器会检查Employee是否具有公有的无参构造函数。
        // 若没有则会有The Employee must have a public parameterless constructor 错误。
        ItemFactory<Employee> EmployeeFactory = new ItemFactory<Employee>();
    }
    

C#中的@符号用法

  • 限定字符串

    用 @ 符号加在字符串前面表示其中的转义字符“不”被处理。

    如果我们写一个文件的路径,例如"D:/文本文件"路径下的text.txt文件,不加@符号的话写法如下:

    string fileName = "D://文本文件//text.txt";

    如果使用@符号就会比较简单:

    string fileName = @"D:/文本文件/text.txt";

  • 让字符串跨行

    有时候一个字符串写在一行中会很长(比如SQL语句),不使用@符号,一种写法是这样的:

    string strSQL = "SELECT * FROM HumanResources.Employee AS e" +
    " INNER JOIN Person.Contact AS c" +
    " ON e.ContactID = c.ContactID" +
    " ORDER BY c.LastName";
    

    加上@符号后就可以直接换行了:

    string strSQL = @"SELECT * FROM HumanResources.Employee AS e
    INNER JOIN Person.Contact AS c
    ON e.ContactID = c.ContactID
    ORDER BY c.LastName";
    
  • 在标识符中的用法

    C#是不允许关键字作为标识符(类名、变量名、方法名、表空间名等)使用的,但如果加上@之后就可以了,例如:

    namespace @namespace
    {
        class @class
        {
            public static void @static(int @int)
            {
                if (@int > 0)
                {
                    Debug.Log("Positive Integer");
                }
                elseif (@int == 0)
                {
                    Debug.Log("Zero");
                }
                else
                {
                    Debug.Log("Negative Integer");
                }
            }
        }
    }
    

C#的装箱和拆箱

装箱(boxing)和拆箱(unboxing)是C#类型系统的核心概念

  • 是不同于C与C++的新概念!通过装箱和拆箱操作,能够在值类型和引用类型中架起一做桥梁。

  • 装箱和拆箱是值类型和引用类型之间相互转换所要执行的操作。

  • 装箱是将值类型转换为引用类型,拆箱是将引用类型转换为值类型。

为何需要装箱?(为何要将值类型转为引用类型?)

  • 一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意类型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。

  • 另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。

装箱/拆箱的内部操作

  • 装箱

    • 新分配托管堆内存
    • 将值类型的实例字段拷贝到新分配的内存中
    • 返回托管堆中新分配对象的地址
  • 拆箱

    • 检查对象实例,确保它是给定值类型的一个装箱值
    • 将该值从实例复制到值类型变量中

装箱操作和拆箱操作是要额外耗费cpu和内存资源的,所以在c# 2.0之后引入了泛型来减少装箱操作和拆箱操作的消耗。

Task RunSynchronously()和Start()的使用与区别

  • RunSynchronously():在当前任务调度程序上同步运行任务。

  • Start():启动任务,将其调度到当前任务调度程序执行。

    Start()尝试并将任务排队到调度程序,而RunSynchronously()将尝试并内联执行它,如果失败(返回false),它将只是排队。

    在Unity的主线程上使用Start()会创建一个新的线程,而RunSynchronously()则会在主线程上执行。

ConfigureAwait(false)能做什么呢?

默认情况下,当你使用async/await时,它将在开始请求的原始线程上继续运行(状态机)。但是,如果当前另一个长时间运行的进程已经接管了该线程,那么你就不得不等待它完成。要避免这个问题,可以使用ConfigureAwait的方法和false参数。当你用这个方法的时候,这将告诉Task它可以在任何可用的线程上恢复自己继续运行,而不是等待最初创建它的线程。这将加快响应速度并避免许多死锁。

但是,这里有一点点损失。当你在另一个线程上继续时,线程同步上下文将丢失,因为状态机改变。这里最大的损失是你会失去归属于线程的Culture和Language,其中包含了国家语言时区信息,以及来自原始线程的HttpContext.Current之类的信息。因此,如果你不需要以此来做多语系或操作任何HttpContext类型设置,则可以安全地进行此方法的调用。注意:如果需要Culture或Language,可以始终在await之前存储当前相关状态值,然后在await新线程之后重新应用它。

⚠️ **GitHub.com Fallback** ⚠️