Нередко в программе приходится работать с различными ресурсами типа файловых и сетевых потоков, которые надо явным образом закрывать после работы. В общем случае проблемы нет - мы открыли файл, поработали с ним и закрыли. Однако при работе с подобными ресурсами могут возникать исключения, что может привести к тому, что программа используемый ресурс должным образом не будет освобожден. И конструкция try...finally как раз позволяет выполнить освобождение ресурсов, даже если при работе с ними возникло исключением. Данную конструкцию можно применять при работе с файловыми, сетевыми потоками, в общем с различными ресурсами, для которых требуется освобождение, например, закрытие потока. Эта конструкция имеет следующий синтаксис:
try код_работы_с_ресурсом finally код_особождения_ресурса
В блок try помещается код, который потенциально может сгенерировать исключение. В блоке finally происходит освобождение использованного ресурса. Причем блок finally выполняется даже в том случае, если в блоке try возникло исключение.
Например, возьмем следующую программу:
let writeList (items: string list) startIndex endIndex = let writer: System.IO.StreamWriter = new System.IO.StreamWriter("test.txt") try for i = startIndex to endIndex do writer.WriteLine(items[i]) () // блок try возвращает значение типа unit finally writer.Flush() printfn "Закрытие потока" writer.Close() let people = ["Tom"; "Bob"; "Sam"] writeList people 0 3 printfn "Запись завершена"
Здесь определена функция writeList, которая записывает в файл список строк. Список строк передается в качестве первого параметра. Второй и третий параметры представляют соответственно начальный и конечный индексы элеменетов для записи в списке. Для записи в файл применяется встроенный тип .NET System.IO.StreamWriter. Через конструктор он принимает путь к файлу, в который надо выполнить запись - в данном случае это файл с именем "test.txt", который будет располагаться в текущей папке программы.
Внутри функции определяется конструкция try...finally
. В блоке try в цикле проходим по элементам списка между индексами startIndex и
endIndex и элемент списка между этими индексами записываем в файл с помощью функции WriteLine
writer.WriteLine(items[i])
В конце блока try возвращается значение типа unit - ()
Переданные индексы startIndex и endIndex могут быть за пределами допустимого диапазона для переданного списка. Соответственно, если как минимум один из индексов вне диапазона, то при работе программы возникнет исключение.
В блоке finally осуществляем закрытие ресурсов, которые связаны с объектом StreamWriter, то есть закрытие файлового потока.
finally writer.Flush() printfn "Закрытие потока" writer.Close()
Здесь начала сбрасываем все незаписанные данные из буфера в файл с помощью метода writer.Flush()
, затем выводим на консоль диагностическое сообщение и в конце собственно
закрываем файловый поток методом writer.Close()
И если мы передадим в функцию writeList нейдествительный индекс, как в следующем случае:
let people = ["Tom"; "Bob"; "Sam"] writeList people 0 3 // индекс 3 - недействительный
то в блоке try возникнет исключение, однако даже несмотря на этого будет выполняться блок finally, который закроет файловый поток.
Например, при выполнении выше расмотренной программы мы получим консольный вывод наподобие следующего:
Закрытие потока System.ArgumentException: Индекс вышел за границы диапазона элементов списка. (Parameter 'n') at Microsoft.FSharp.Collections.PrivateListHelpers.nth[a](FSharpList`1 l, Int32 n) in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4093 at FSI_0001.writeList(FSharpList`1 items, Int32 startIndex, Int32 endIndex) in /Users/eugene/Documents/fsharp/program.fsx:line 5 at <StartupCode$FSI_0001>.$FSI_0001.main@() in /Users/eugene/Documents/fsharp/program.fsx:line 13 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) Остановлено из-за ошибки
Здесь мы видим, что программа ожидаемо завершилась ошибкой, тем не менее мы также видим, что блок finally отработал, а файловый поток был закрыт.
Хотя в примере выше ресурсы освобождаются, тем не мнее исключение не обрабатывается. И для его обработки вызов функции обернем в конструкцию try...with:
let writeList (items: string list) startIndex endIndex = let writer: System.IO.StreamWriter = new System.IO.StreamWriter("test.txt") try for i = startIndex to endIndex do writer.WriteLine(items[i]) () finally writer.Flush() printfn "Закрытие потока" writer.Close() let people = ["Tom"; "Bob"; "Sam"] try writeList people 0 3 with | _ -> printfn "Произошла ошибка..."; () printfn "Запись завершена"
Теперь программа не упадет и завершится как положено:
Закрытие потока Произошла ошибка... Запись завершена
Если блок try из конструкции try...finally
возвращает некоторое значение, то внешний блок with, который обрабатывает исключение, должен возвращать
значение этого же типа. В примере выше это значение unit
(то есть фактически ничего не возвращается). Но это может быть и какое-то определенное значение:
let writeList (items: string list) startIndex endIndex = let writer: System.IO.StreamWriter = new System.IO.StreamWriter("test.txt") try for i = startIndex to endIndex do writer.WriteLine(items[i]) Some(endIndex - startIndex + 1) //возвращаем количество записанных элементов finally writer.Close() let printResult result = match result with | Some n -> printfn "Успешно записано %d элементов" n | _ -> printfn "Произошла ошибка..." let people = ["Tom"; "Bob"; "Sam"] let result1 = try writeList people 0 2 with | _ -> None let result2 = try writeList people 0 3 with | _ -> None printResult result1 printResult result2
Здесь в блоке try в try..finally
возвращаем количество записанных элементов через Some. А при обработке ошибки в блоке with возвращаем None.
А в функции printResult в завимости от значения результата - Some(n) или None выводим то или иное сообщение на консоль.