【问题标题】:Functional way of handling resource clean up in F#在 F# 中处理资源清理的函数式方法
【发布时间】:2016-12-24 19:19:31
【问题描述】:

您好,这是 C# 语言如何处理 Com 互操作资源管理的示例。 orginal source

Excel.Application app = null;
Excel.Workbooks books = null;
Excel.Workbook book = null;
Excel.Sheets sheets = null;
Excel.Worksheet sheet = null;
Excel.Range range = null;

try
{
    app = new Excel.Application();
    books = app.Workbooks;
    book = books.Add();
    sheets = book.Sheets;
    sheet = sheets.Add();
    range = sheet.Range["A1"];
    range.Value = "Lorem Ipsum";
    book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
    book.Close();
    app.Quit();
}
finally
{
    if (range != null) Marshal.ReleaseComObject(range);
    if (sheet != null) Marshal.ReleaseComObject(sheet);
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (book != null) Marshal.ReleaseComObject(book);
    if (books != null) Marshal.ReleaseComObject(books);
    if (app != null) Marshal.ReleaseComObject(app);
} 

我个人认为上面的代码是合理和必要的。但它不是功能或 F# 方式。我最终在嵌套 try...finally 和 try...with 的不同级别定义了所有这些 com 变量,因为必须在 try 块之前定义变量,所以清理代码存在于 finally 块和 with 块中。这是非常混乱的。

如何在 F# 中正确实现相同的功能?有点讽刺的是,互联网上有很多例子解释了如何使用 F# 和互操作来展示 F# 的力量。然而,他们都没有讨论如何管理 com 资源清理。

欢迎任何关于良好模式的建议。

【问题讨论】:

  • 非常简短,因为我在手机上:使用 IDisposable 对象(如果 Com 没有实现 IDisposable,则使用包装器),并使用 F# use 表达式。它类似于let,只是它在对象超出范围时调用 Dispose()。

标签: c# f# com functional-programming excel-interop


【解决方案1】:

您可以创建一个计算表达式,在 try/catch 中调用每个步骤,并在完成后在 finally 中释放。我们可以为 create/finalize 创建一个带有插件功能的构建器,这样我们就可以看到发生了什么。

type FinalizationBuilder(oncreate, onfinal) =
  member __.Bind(m, f) = 
    oncreate(box m)
    try
      try
        f m
      with ex ->
        Choice2Of2 ex.Message
    finally
      onfinal(box m)
  member __.Return(m) = Choice1Of2 m
  member __.Zero() = Choice1Of2()

然后您需要一个 COM 工作流,它在完成时仅释放 COM 组件。

let com = new FinalizationBuilder(ignore, System.Runtime.InteropServices.Marshal.ReleaseComObject >> ignore)

你可以这样使用它:

[<EntryPoint>]
let main _ = 
  com { 
    let! app = new Excel.Application()
    let! books = app.Workbooks
    let! book = books.Add()
    // ...
    app.Quit()
  } |> ignore
  0

我没有安装 excel,但我可以用异常和 printfns 模拟它。

let demo = new FinalizationBuilder(printfn "Created %A", printfn "Released %A")

[<EntryPoint>]
let main _ = 
  demo { 
    let! x = 1
    let! y = 2
    let! z = 3
    return x + y + z
  } |> printfn "Result: %A"
  0

// Created 1
// Created 2
// Created 3
// Released 3
// Released 2
// Released 1
// Result: Choice1Of2 6

或有例外:

[<EntryPoint>]
let main _ = 
  demo { 
    let! x = 1
    let! y = 2
    let! z = failwith "boom"
    return x + y + z
  } |> printfn "Result: %A"
  0

// Created 1
// Created 2
// Released 2
// Released 1
// Result: Choice2Of2 "boom"

说了这么多,看来这些都不是必需的。一个简单的GC.Collect(); GC.WaitForPendingFinalizers() 可以解决这个问题,而不需要任何这个:https://stackoverflow.com/a/25135685/171121

【讨论】:

  • 嗨,Dax,我现在无法测试代码,但我可以问一下吗! z = failwith "boom" 代码在创建 z 期间失败,而不是执行 x + y + z。这意味着如果程序将使用 interop excel 对象来做某事,那么每一行逻辑都必须使用 let 绑定来编写,对吗?如果 x + y + z 中发生异常并且它没有包含在 let 绑定中,则错误句柄和释放逻辑将不适用?
  • @casbby 不,计算表达式处理其中任何地方发生的任何异常并应用发布逻辑。因此,如果您将第一个示例更改为return x + y + z + (failwith "boom"),它将释放到目前为止创建的所有内容(x、y、z)并返回Choice2Of2 "boom"
  • 确实!它有效地为每一层的 let! 创建了一个嵌套的 try ..with ...finally!使我免于手动定义它们。非常感谢你这么快的回复。还有一个问题。我不完全理解零()。一般来说,在这种情况下,它的目的是什么?无论是否指定 zero(),您的代码都将正常工作。
  • @casbby 如果表达式中没有return,则它是必要的。例如在第一个示例中,表达式以app.Quit() 结尾并且没有return
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-25
  • 1970-01-01
  • 2022-10-19
  • 2019-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多