我正在寻找一组程序集中的特定方法。为了找到我的方法,我写了这段代码
module MethodNameExtractor
open System
open System.IO
open System.Reflection
let ListMethodsInAssembly (input : AssemblyName) =
try
let appd = AppDomain.CreateDomain("temp")
try
let assm = Assembly.Load(input)
assm.GetTypes()
|> Array.toSeq
|> Seq.fold (fun accm ty -> Seq.append accm (ty.GetMethods() |> Array.toSeq)) Seq.empty
|> Seq.filter (fun x -> x.Name <> "GetType" && x.Name <> "ToString" && x.Name <> "GetHashCode" && x.Name <> "GetType")
finally
AppDomain.Unload(appd)
with
| :? System.Exception -> Seq.empty
let ListAllDlls =
let rec expandDir (parent : DirectoryInfo) accm =
let files = parent.GetFiles() |> Array.toSeq |> Seq.map (fun f -> try Some(AssemblyName.GetAssemblyName f.FullName) with | :? System.Exception -> None)
let accm = Seq.append files accm
let subDirs = parent.GetDirectories() |> Array.toSeq
Seq.fold (fun accm d -> expandDir d accm) accm subDirs
let basePath = "c:\\Windows\\Microsoft.NET\\assembly\\"
let loc = ["GAC_64"; "GAC_MSIL";] |> List.toSeq
Seq.fold (fun acc l ->
let path = basePath + l
expandDir (new DirectoryInfo(path)) acc
)
Seq.empty
loc
[<EntryPoint>]
let main args =
ListAllDlls
|> Seq.fold (
fun accm x ->
match x with
| Some(a) -> Seq.append accm (ListMethodsInAssembly a)
| None -> accm
) Seq.empty
|> Seq.iter (fun x -> printfn "%s" x.Name)
0
我的希望是因为我使用的是Sequences和Tail Recursion ....我不应该消耗太多内存,如果我可以让我的代码运行得足够长,我可以获得所有程序集中的方法列表。
我的代码非常简单...获取所有目录和子目录中的DLL列表。
加载DLL并获取类型...然后从类型中获取所有公共方法...
但代码处理大约200万条记录,然后获得StackOverflow异常。
我已经在使用Tail递归和Sequence ...如何进一步改进此代码?
答案 0 :(得分:3)
此函数不是尾递归的:
let ListAllDlls =
let rec expandDir (parent : DirectoryInfo) accm =
let files = parent.GetFiles() |> Array.toSeq |> Seq.map (fun f -> try Some(AssemblyName.GetAssemblyName f.FullName) with | :? System.Exception -> None)
let accm = Seq.append files accm
let subDirs = parent.GetDirectories() |> Array.toSeq
Seq.fold (fun accm d -> expandDir d accm) accm subDirs
请注意,对expandDir
的递归调用的结果在调用函数中使用(特别是在传递给Seq.fold
的函数中。这可以防止尾递归,因为调用堆栈帧仍然在使用时进行递归调用。
这是一个树递归,因此没有一种很好的方法可以使尾递归。有关使用继续传递解决此问题的信息,请参阅here。继续传递的本质是维护你尚未作为lambda使用的分支列表。
答案 1 :(得分:3)
您的代码存在一些问题:
Seq.fold
,Seq.append
和Seq.empty
的组合在序列表达式中的Seq.collect
或yield!
执行效果不佳。Array.toSeq
或List.toSeq
。
try ... with | :? System.Exception
可缩短为try ... with _
您的expandDir
函数不是尾递归的。如果将其更改为使用序列表达式,则会使用CPS进行优化,因此不会发生StackOverflow。
这是一个改进版本。我认为它更具可读性,可能更快。
let ListMethodsInAssembly (input : AssemblyName) =
try
let appd = AppDomain.CreateDomain("temp")
try
let assm = Assembly.Load(input)
assm.GetTypes()
|> Seq.collect (fun ty -> ty.GetMethods())
|> Seq.filter (fun x -> x.Name <> "GetType" && x.Name <> "ToString" && x.Name <> "GetHashCode" && x.Name <> "GetType")
finally
AppDomain.Unload(appd)
with _ -> Seq.empty
let ListAllDlls =
let rec expandDir (parent : DirectoryInfo) =
let getAssemblyNameFromFile (f : FileInfo) =
try
[ AssemblyName.GetAssemblyName f.FullName ]
with _ -> []
seq {
for f in parent.GetFiles() do
yield! getAssemblyNameFromFile f
for subDir in parent.GetDirectories() do
yield! expandDir subDir
}
let basePath = "c:\\Windows\\Microsoft.NET\\assembly\\"
let loc = ["GAC_64"; "GAC_MSIL";]
seq {
for l in loc do
let path = basePath + l
yield! expandDir (DirectoryInfo(path))
}
[<EntryPoint>]
let main args =
ListAllDlls
|> Seq.collect ListMethodsInAssembly
|> Seq.iter (fun x -> printfn "%s" x.Name)
0