Essential C# 6.0 C#学习笔记 第四章 方法和参数

Essential C# 6.0 学习笔记

由于博主对C#的使用比较多,但是对C#的理解还是停留在表面,所以开创此系列篇章,详细记录在学习Essential C# 6.0 这本书时候遇到的问题,以及优秀的知识点的记录,还有自己对一些知识点的理解以及拓展。

该系列文章绝不是简单的阐述概念,而是将知识点互相串通,融入使用。欢迎大家关注,文章会持续更新!!!


第四章 方法和参数

在这里插入图片描述


1.命名空间

命名空间是分类和组合相关类型的组织机制
在一个类型所在的命名空间中,能够找到和它有关的其他类型。此外,重名的两个或更多类型只要在不同命名空间中,就没有歧义。

命名空间是一种分类机制,用于组合功能相关的所有类型。命名空间是分级的,级数任意,但是很少见到超过六级的命名空间。
命名空间主要用于按照功能领域组织类型,以便更容易查找和理解他们。除此之外,命名空间还有助于防止类型名称发生冲突
两个都叫Student的类型只要在不同的命名空间下面,编译器就能区分他们。

2.类型名称

类型名称是在命名空间下面的
调用静态方法的时候,如果目标方法和调用者不在同一个类型中,就需要使用类型名称限定符。(比如“using static”指令就可以省略类型名称)

3.使用别名

别名两个最常见的用途就是消除两个同名类型的歧义和缩写长名称
别名是在using指令起作用的范围内可以使用的替代名称
在这里插入图片描述

4.调用栈和调用点

代码执行的时候,我们可能会调用其他方法,其他方法可能会调用更多的其他方法。那么,每次在调用新方法的时候,“运行时(VES:CLR)”都创建一个“栈帧”,其中包含的内容涉及传给新调用的实参、新调用的局部变量以及方法返回时应该从哪里恢复等。这样形成的一系列栈帧成为调用栈(call stack)。随着程序的复杂度提高,多次调用后这个调用栈会变大。然而,当我们调用结束的时候,调用栈会发生收缩,直到调用另一个方法。我们用栈展开(stack unwinding)这个词描述从调用栈中删除栈帧的过程。栈展开的顺序通常与方法调用的顺序相反。方法调用完毕之后,执行会返回到调用点(call site),也就是最初发出方法调用的位置

5.引用类型和值类型参数的对比

就名字可以看出,对应引用类型的变量,他的值实际上是对数据实际存储位置的引用。“运行时”如何表示引用类型变量的值,这是“运行时”的实现细节。一般都是用数据实际存储的内存地址来表示,但并非一定如此。
如果是以值传递的方式将引用类型作为参数传入,引用(地址)本身会从调用者复制给方法参数。结果就是,虽然目标方法改变不了调用者变量的值,即改变不了引用(地址)本身但是可以更改引用所指向的数据

另一方面,对于值类型的参数,参数获得的是值的副本,所以在被调用的方法中更改参数不会影响调用者的原始变量

6.引用参数ref和输出参数out的区别

引用参数ref
首先,ref修饰的参数是以传引用的方式传递变量。这个关键字使得参数以传引用的方式传递,使被调用的方法可以用新值来更新调用者的变量。
调用者应该对传引用的变量进行初始化,因为目标方法可能直接从ref参数读取数据而不对其进行赋值。
ref参数只是传递的变量的别名,换言之,只是为现有的变量分配了参数名,而不是创建新变量并将实参的值复制给他。

代码演示

static int x = 10;
    static void Main()
    {
    
    
        unsafe
        {
    
    
            fixed(int* pointer = &x){
    
    
                Console.WriteLine("Address of x is 0x{0:x}", (int)pointer);
            }
        }
        int b = 20;
        Add(ref x,ref  b);
    }

    static void Add(ref int a,ref int b)
    {
    
    
        unsafe
        {
    
    
            fixed (int* pointer = &a)
            {
    
    
                Console.WriteLine("Address of a is 0x{0:x}", (int)pointer);
            }
        }
        a += b;
    }

我们发现,将引用参数输出地址之后,发现和原来的地址一样。就代表如果我们此时修改a的值,x也会跟着被修改。这也就让我们发现了本质,ref参数就是将原来的变量创建了一个引用,我们其实操作的还是原来的那个变量
在这里插入图片描述
输出参数out
我们有些时候,需要引用一个参数,但是不需要对参数进行修改,而只需要赋值。
比如说,我们想要让方法有多个返回值
这时候最安全的办法就是传入一个未初始化的局部变量,也就是用out修饰的变量
但是,一旦我们用了out参数,我们就必须要将它进行赋值,如果不赋值,就会报错
在这里插入图片描述

7.参数数组

使用参数数组,可以将类型相同,数量可变的多个参数传给函数
类似:
在这里插入图片描述

1.参数数组并不一定是方法的唯一参数,但一定是方法的最后一个参数
2.参数数组必须是类型安全的,实参必须兼容于参数数组中的类型
3.调用者可以显式地使用数组传递,不一定非得用实参
4.如果函数必须要一个参数的情况下,应该这样定义函数参数列表
因为参数数组可以为空,但是如果我们定义了int first,那么就必须要传入一个参数

    static void Add(int first,params int[] arrays)
    {
    
    
        //正确
    }
    static void Add(params int[] arrays)
    {
    
    
        //错误
    }

8.可选参数

C#从4.0开始就支持可选参数
1.可选参数必须得是最后的参数
2.可选参数的值必须得是默认值,换言之,就是必须得在编译时确定的值
3.

 static void Add(int x=10)
    {
    
    
        
    }

9.命名参数

调用者可以显式地为某个参数赋值,而不是根据索引位置来赋值
这个便利的代价便是牺牲接口的灵活性

因为函数的参数名是可以任意更改的,如果我们更改了函数的参数名,但是在调用函数的地方,没有修改参数名,那么就会报错

在这里插入图片描述

10.方法解析

当编辑器必须从一系列适用的方法中选择一个最适合某个特定调用的方法时,会选择拥有最具体的参数类型的那个方法。
假设有两个适用的方法,那么编辑器会自动如下选择

1.实参类型到形参类型完全匹配的方法
2.隐式转换成形参,更具体的派生方法

这里因为10直接和int完美匹配
如果没有int参数的方法,那么会默认选择long参数的方法
因为long比object更加具体
在这里插入图片描述

11.try catch语句

这个过程被称为 捕获异常
首先用try块将可能发生异常的代码包围起来
try块之后必须紧跟着一个或多个catch块(或/和一个finally块)
假如一直找不到合适的catch块,引发的异常就会变成一个未处理的异常,就好像没有进行异常处理一样

其次,处理异常的顺序非常重要。catch块必须按照从最具体到最不具体排列。只要控制离开try块,finally块就会执行。finally块的作用是提供一个最终位置,在其中放入无论是否发生异常都要执行的代码。finally块最适合用来执行资源清理

但是,catch语句块会先报错然后再执行finally,这样合理吗?
对于“运行时(CLR)”来说,合理。对于未处理的异常,“运行时”的行为是由具体实现定义的,任何行为都合法!“运行时”已经检查了调用栈上面的所有栈帧,发现没有任何一个栈帧关联了能够引发异常相匹配的catch块
如果发现是未处理的异常,“运行时”会首先检查机器是否安装了调试器,然后提示程序员。默认是在控制台上面打印出未处理的异常,再看看是否有哪些finally块可供运行。

所以,“运行时”并非一定要运行finally块,实现可以选择这样做也可以不这样做。

12.Exception类继承

所有的异常都派生自System.Exception类,所以他们都可以用catch块进行处理。然而,更好的处理办法是用catch块来处理更加具体的派生类型,这正是规定catch块必须从“最具体”到“最不具体”排列的原因
在这里插入图片描述

13.常规catch块

没有指定数据类型的catch块被称为常规catch块,它等价于获取object数据类型的catch块
但是它没有办法捕获有关异常的任何信息,只是可以捕获到异常

14.通过throw语句报告错误

如果引发一个异常,那么会使执行从异常的引发点跳转到引发异常类型兼容的第一个catch块。
在下面的例子中第二个空的catch块会捕获到throw的exception

        int b = 20;
        try
        {
    
    
            Console.WriteLine("begin");
            throw new Exception("hhhh");
            Console.WriteLine("end");
        }
        catch (FormatException exception)
        {
    
    
            Console.WriteLine("FormatException");
            
        }
        catch 
        {
    
    
            Console.WriteLine("Unknow Exception");
        }

        Console.WriteLine("over");

在这里插入图片描述
但是,有的时候catch块能够捕获到异常,但是不能正确或者完整地处理它。在这种情况下,可以让这个catch块重新引发异常,具体的办法是使用一个单独的throw语句,不要在后面跟任何异常
在这里插入图片描述
throw保持了异常中的调用栈信息,这里就是保持了异常最初的调用栈引发位置,让我们快速追踪到异常
但是,如果用的是throw exception那么就会把信息替换成当前调用栈信息,而我们想要的是原始调用栈,所以,直接一个throw就行!

规范:
1.要在捕获并重新引发异常时候使用空的throw语句,以便保持调用栈
2.要通过引发异常而不是返回错误码来报告执行失败
3.不要让公共成员将异常作为返回值或者out参数。要通过异常来指明错误。不要通过将他们作为返回值来指明错误

总结

本章讨论了方法的声明和调用的细节,包括如何使用关键字out和ref传递变量而非其值,还有异常机制
下一章开始讨论类,并解释它如何将方法(行为)和字段(数据)封装为一个整体。

猜你喜欢

转载自blog.csdn.net/m0_48781656/article/details/124551880