在F#中处理资源清理的功能方式

cas*_*bby 4 c# com f# functional-programming excel-interop

嗨,这是C#语言如何处理Com互操作资源管理的例子.原始来源:

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);
} 
Run Code Online (Sandbox Code Playgroud)

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

我怎样才能在F#中正确实现相同的功能?有点讽刺的是,互联网上有很多例子说明如何使用F#和interop作为展示F#功能的方式.但是,没有人讨论如何管理com资源清理.

关于良好模式的任何建议都是适用的.

Dax*_*ohl 6

您可以创建一个计算表达式来调用try/catch中的每个步骤,并在完成时释放.我们可以使用插件函数为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()
Run Code Online (Sandbox Code Playgroud)

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

let com = new FinalizationBuilder(ignore, System.Runtime.InteropServices.Marshal.ReleaseComObject >> ignore)
Run Code Online (Sandbox Code Playgroud)

你这样使用它:

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

我没有安装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
Run Code Online (Sandbox Code Playgroud)

或者例外:

[<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"
Run Code Online (Sandbox Code Playgroud)

总而言之,看起来无论如何都不需要这样做.一个简单的GC.Collect(); GC.WaitForPendingFinalizers()解决方案可以解决这个问题而不需要任何这个:https://stackoverflow.com/a/25135685/171121

  • @casbby如果表达式中没有`return`,那是必要的.例如在第一个例子中,表达式以`app.Quit()`结尾,而没有`return`. (2认同)