F#入门学习(九)

  1. 异常处理
    共有两种异常,一种是F#异常,一种是.NET异常,和之前提到的F#库和.NET库一样,看这个语言的视角不同,分法也就不一样。
    F#需要用户定义后才能使用,.NET异常可以直接用。
    所有.NET异常都继承其框架里面的异常基类Exception类,Exception类有很多属性:
  • StackTrace属性 追踪错误发生位置,包括源文件和行号等信息。
  • InnerException属性 若存在很多异常之间互有联系。例如,发生了a异常,导致了b异常,那么a异常就会被保存在b异常的这个属性中,我们可以查看。
  • Message属性 提供异常的详细信息。
    还有其余的,用到的时候可以翻阅相关资料。
  1. 使用try…with表达式处理代码错误
    1.使用try捕获代码错误,产生一个异常对象。
    2.使用with和模式匹配编写异常处理代码。

    try
         表达式
      with
          | 模式1 ->表示式1
          | 模式2 ->表示式2
    

    使用的时候需要注意两点:一是表达式返回值类型必须一致。二是若在with中没有找到相应的模式匹配,那么这个异常会向上传播,直到找到匹配的处理程序。

  2. 使用try…with处理.NET异常

    let dividel x y = 
        try 
            Some (x/y)
        with 
            | :?System.DivideByZeroException->
            printf"除数为0!";None
    
    let result1 = dividel 100 0
    

    //除数为0!val dividel : x:int -> y:int -> int option
    val result1 : int option = None

  • 代码里面有个Some函数,这不是我们自己写的,是系统函数在这里插入图片描述
    表明泛型转换成泛型,具体作用可能是专门防止异常发生。None是一个取值,F#直接把最后一个值当成表达式的最后返回值。
  • 还有个:?符号,这个符号用来表明类型测试,测试是不是我们写好的类型。.NET类型需要用,F#类型不需要。
  1. 使用as关键字对.NET异常重命名

        let divide2 x y = 
          try 
              Some (x/y)
          with 
              | :?System.DivideByZeroException as ex
               -> printf"异常!%s"(ex.Message);None  //调用Massage属性
      
      let result2 = divide2 100 0 
    

    //异常!尝试除以零。val divide2 : x:int -> y:int -> int option
    val result2 : int option = None

  2. 对于同一个异常,使用when条件实现多种匹配
    按照上面我们的方法,一个异常只能匹配一种模式,那么如何实现一种异常根据要求返回不同的值呢?

    let divide3 x y flag = 
       try 
           x/y
       with 
           | ex when flag -> printfn"TRUE: %s"(ex.ToString());0  //ex是exn类型。根据flag取值不同,即使除数都是0,也返回不同的值。
           | ex when not flag ->printfn"FLASE:%s"(ex.ToString());1  //这里的0和1是最后赋值的意思。
    
    let result3 = divide3 100 0 true
    let result4 = divide3 100 0 false
    

    //TRUE: System.DivideByZeroException: 尝试除以零。
    在 FSI_0007.divide3(Int32 x, Int32 y, Boolean flag) 位置 C:\VisualStudio\Projects\myfsharp_test1\myfsharp1.fs:行号 3
    FLASE:System.DivideByZeroException: 尝试除以零。
    在 FSI_0007.divide3(Int32 x, Int32 y, Boolean flag) 位置 C:\VisualStudio\Projects\myfsharp_test1\myfsharp1.fs:行号 3
    val divide3 : x:int -> y:int -> flag:bool -> int
    val result3 : int = 0
    val result4 : int = 1

  3. 上面讲的都是.NET异常,现在看看F#异常
    使用if表达式对可能发生的错误进行预判,如果发生了错误,就生成异常,然后给异常处理程序处理。
    我们生成异常的时候,有三种方式。我们用下面的三点分别讲解。

  4. rasie函数
    用rasie函数生成制定的异常类型,可以是F#异常,也可以是是.NET异常。
    语法:rasie(异常类型表达式)

    let divide x y = 
       if (y = 0) then 
            raise(System.ArgumentException("除数不能为0!")) //System.ArgumentException("除数不能为0!")括号里面是传给该异常Massage属性的信息。
        else 
            x/y
    let result = divide 100 0
    

    //System.ArgumentException: 除数不能为0!
    在 FSI_0010.divide(Int32 x, Int32 y) 位置 C:\VisualStudio\Projects\myfsharp_test1\myfsharp1.fs:行号 3
    在 <StartupCodeFSI0010>.FSI_0010>.FSI0010>.FSI_0010.main@() 位置 C:\VisualStudio\Projects\myfsharp_test1\myfsharp1.fs:行号 6
    已因出错而停止

  5. failwith函数
    用failwith函数生成F#异常。
    语法:failwith 串类型错误信息。串类型是文本字符串或者string类型的值,它将成为异常的Massage属性

    let divideFailwith x y = 
       if (y = 0) then 
            failwith"除数不能为0!"
        else 
            x/y
    let testDivideFailwith x y =
        try
            divideFailwith x y
        with
            | Failure(msg)->printfn"%s" msg;0
    let result = testDivideFailwith 100 0
    

    //除数不能为0!
    val divideFailwith : x:int -> y:int -> int
    val testDivideFailwith : x:int -> y:int -> int
    val result : int = 0
    这段代码刚开始看有点莫名其妙,我是这么理解的:发生了错误,failwith就把它保存下来,(理解为动作)保存的不是错误,是发生了错误,保存我们怎么描述或者通知错误的话。这句话有个代号叫Failure,(理解为名词)Failure可以呈现我们的话。用专业的语句来说:Failure的功能是生成一个System.Exception异常对象,接收string类型的参数,也就是msg也就是Massage属性的取值,用来描述异常信息。
    为什么非要多此一举用Failure呢?因为failewith生成了异常但是这个实例好的异常名称我们没办法获得,我们就使用Failure取这些实例好的异常的属性,就变相的分辨出了不同的异常。

  6. invalidArg函数
    用 invalidArg函数生成.NET异常。
    语法: invalidArg 参数名 串类型错误信息_参数名是字符串,串类型是字符串或者string类型的值,它将成为异常的Massage属性_

    //定义国内7个法定节假日数组
    let holidays = [|"元旦";"春节";"清明节";"端午节";
                        "劳动节";"中秋节";"国庆节";""|]
    //lookupHoliday的功能是给出下标,求出对应节日名称
    let lookupHoliday festival = 
        if (festival > 7 || festival < 1)
            then invalidArg "fastival" (sprintf"被传递的参数不在1--7的范围内,为%d。"festival)
        holidays.[festival - 1]
    let x1 = lookupHoliday 7
    let x2 = lookupHoliday 1
    

    //val holidays : string [] =
    [|“元旦”; “春节”; “清明节”; “端午节”; “劳动节”; “中秋节”; “国庆节”; “”|]
    val lookupHoliday : festival:int -> string
    val x1 : string = “国庆节”
    val x2 : string = “元旦”

    //定义国内7个法定节假日数组
    let holidays = [|"元旦";"春节";"清明节";"端午节";
                        "劳动节";"中秋节";"国庆节";""|]
    //lookupHoliday的功能是给出下标,求出对应节日名称
    let lookupHoliday festival = 
        if (festival > 7 || festival < 1)
            then invalidArg "fastival" (sprintf"被传递的参数不在1--7的范围内,为%d。"festival)
        holidays.[festival - 1]
    let x3 = lookupHoliday 8
    

    //System.ArgumentException: 被传递的参数不在1–7的范围内,为8。
    参数名: fastival
    在 FSI_0016.lookupHoliday(Int32 festival) 位置 C:\VisualStudio\Projects\myfsharp_test1\myfsharp1.fs:行号 7
    在 <StartupCodeFSI0016>.FSI_0016>.FSI0016>.FSI_0016.main@() 位置 C:\VisualStudio\Projects\myfsharp_test1\myfsharp1.fs:行号 9
    已因出错而停止
    数组的定义暂时还没有讲到,这里先看着就可以,可以看到invalidArg函数直接根据参数生成相应的异常。
    其实还可以复习,F#函数调用的时候直接跟着参数,看第八点,.NET库调用的时候一般都要加括号,看第九点。

  7. 异常处理方法:try…finally表达式
    学过C++或者java的人可能对此有所了解,但是F#和他们是有区别的。
    try…with和try…finally是两个不同的语句,不可以平行使用,必须嵌套使用。
    try…with语句就是用try捕获异常,with处理异常。
    try…finally语句执行try语句没有问题就直接执行然后执行finally结束,如果有问题,那么也不管问题是什么,系统自己一层层往上找错误,系统决定怎么处理就怎么处理,但是处理之前还是要执行finally,然后系统该干啥干啥,干完之后结束。
    一般,finally中包含着一定会执行的代码,比如资源清理操作等等,它不像with那样给出异常语句,finally一般不包含具体的异常代码。

    let divide x y = 
        let stream : System.IO.FileStream = System.IO.File.Create("test.txt")
        let writer: System.IO.StreamWriter = new System.IO.StreamWriter(stream)
        try
            writer.WriteLine("test1");
            Some(x/y)
        finally
            writer.Flush()
            printfn"closing stream"
            stream.Close()
    
    let result = 
        try 
            divide 100 0
        with
            | :?System.DivideByZeroException->printfn"exception handled.";None
    

    //closing stream
    exception handled.
    val divide : x:int -> y:int -> int option
    val result : int option = None
    我们先来看看divide这段代码,首先尝试写入文本,在尝试计算x除y。
    我们最后调用的时候,想计算100除0,这肯定会有异常,有了之后我们肯定会执行with语句的异常处理。
    我们计算100除0的时候调用第一段的divide函数,这个函数写了一个test1文本,然后尝试运算x除y,尝试呀尝试,发现有问题,但是就是不管他,他知道系统会管的,**在系统管之前,把finally后面的代码执行了,**也就是刷新缓冲区,关闭数据流,关闭数据流。终于轮到系统处理那个异常了,怎么处理呢?系统一看有with,就直接用with处理了,打印一句处理完成结束。
    下面是两个我们不熟悉的函数的解释:
    在这里插入图片描述
    在这里插入图片描述

  8. 自定义F#异常处理程序
    语法:exception 异常名称 of 参数类型
    异常名称是我们给这个异常起的名字,参数类型是我们提供这种参数类型才可能出发异常。

    exception Error1 of string
    //定义异常Error1,它包含一个string类型参数
    exception Error2 of string * int 
    //定义异常Error2,它包含一个string * int元组类型参数
    
    //定义一个包含两个参数的函数
    let function1 x y = 
        try 
            if x = y then raise(Error1("x")) //相等时出发Error1
            else raise(Error2("x",10)) //不相等时出发Error2
        with  //异常匹配处理
            | Error1(str) -> printfn"Error1 %s"str
            | Error2(str,i) -> printfn"Error2 %s %d"str i
    
    //测试实例
    function1 101 101
    function1 7 2
    

    //Error1 x
    //Error2 x 10
    exception Error1 of string
    exception Error2 of string * int
    val function1 : x:'a -> y:'a -> unit when 'a : equality
    val it : unit = ()
    我们定义了一个函数来测试异常,两个参数相等时触发Error1,不相等时触发Error2。str只是一个形参,了里面的值是raise设置好的。
    使用raise函数生成异常,with来匹配异常并处理

  9. 使用try…with和try…finally嵌套处理F#异常

    exception InnerError of string 
    exception OuterError of string
    
    let function1 x y = 
        try 
            try    //使用rasie函数触发异常
                if x = y then raise(InnerError("内部错误"))
                else raise (OuterError("外部错误"))
            with   //使用模式匹配处理异常
            | InnerError(str) ->printfn"输出错误提示1 %s" str 
        finally
            printfn"总是输出该部分。"
    
    let function2 x y = 
        try 
            function1 x y 
        with 
            | OuterError(str) ->printfn"输出错误提示1 %s" str
    
    function2 100 100
    
    function2 100 10
    

    //输出错误提示1 内部错误
    //总是输出该部分。
    //总是输出该部分。
    //输出错误提示1 外部错误
    exception InnerError of string
    exception OuterError of string
    val function1 : x:'a -> y:'a -> unit when 'a : equality
    val function2 : x:'a -> y:'a -> unit when 'a : equality
    val it : unit = ()

  • 调用function2当两个参数都100时,进入function1,先进入第一个try…finally那个匹配,然后进入第二个try…with匹配**产生内部错误异常1,打印。**无论如何finally都会执行,所以输出结果为:
    输出错误提示1 内部错误
    总是输出该部分。
    其实function2自己也有个try…with但是没有匹配的模式,所以没有任何输出。
  • 调用function2当一个参数是100,另一个参数是10时,进入function1,先进入第一个try…finally那个匹配,然后进入第二个try…with匹配**产生外部错误,但是没有相应的模式匹配处理这个错误,所以没有打印。**无论如何finally都会执行,所以输出结果为:
    总是输出该部分。
    现在来到function2自己的try…with,有相应的匹配模式,所以输出:
    输出错误提示1 外部错误
    如果两个都没有处理这个异常呢?按照之前的学习内容,就会一层一层向上寻找异常处理的程序,直到成功。
  1. Exception别名
    我们知道,F#有两个库,一个.NET库,一个F#库。平常使用.NET库中的Exception类比较多,**我们把这个类在F#中做了重新定义,起了个别名叫exn类型。**也就是F#中的异常叫exn类型。
    type exn = System.Exception
    
    我们当然也可以把它定义成自己写的异常类名称:
    type x = System.Exception
    
    type exn = System.Exception
    type x = exn
    
    这两种方法都可以。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值