実行時にCompiledNameが付いた型の元の名前を取得する

正確には実行時じゃないけど解決したのでメモ

元ネタ: http://ja.stackoverflow.com/questions/6177/compilednameが付いた型のf-での名前を取得したい

問題

F#では、CompiledNameAttributeを付けた型は、リフレクションで元の名前を取得できません。

[<CompiledName("Piyo")>]
type Hoge() = class end

というクラスを定義し、リフレクションで型名を取得しようとすると、

> typeof<Hoge>.Name;;
val it : string = "Piyo"

"Piyo"という文字列が返ります。

ソースコード上では"Hoge"という名前でしかアクセスできないのに、取得できないのは何かと不便ですね?

コンパイル前の名前が格納されている場所

アセンブリに、リソースとしてFSharpSignatureData.{AssemblyName}という名前で埋め込まれています。

typeof<Piyo>.Assembly.GetManifestResourceStream("FSharpSignatureData.Library1")

というコードで取得できます。

詳しいフォーマットは分かりませんが、取得できたバイナリを覗いてみると確かに"Hoge"という文字列が入っています。

FSharp.Compiler.Service を使うといい

これらのページを参考にすると、

http://fsharp.github.io/FSharp.Compiler.Service/ja/project.html https://github.com/fsharp/FSharp.Compiler.Service/issues/187

こんな感じのコードで取得できるみたいです。

module CompilerServiceStudy

[<CompiledName("Piyo")>]
type Hoge() = class end

open System.IO

let base1 = Path.GetTempFileName()
let fileName1 = Path.ChangeExtension(base1, ".fs")
let projFileName = Path.ChangeExtension(base1, ".fsproj")
let dllName = Path.ChangeExtension(base1, ".dll")

open Microsoft.FSharp.Compiler.SourceCodeServices

let asm = typeof<Hoge>.Assembly

let checker = FSharpChecker.Create()
let projectOptions =
  let sysLib name =
    System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
    + @"\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\"
    + (name + ".dll")
  let fsCore4300() =
    System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
    + @"\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\4.3.0.0\FSharp.Core.dll"  
  checker.GetProjectOptionsFromCommandLineArgs
    (projFileName,
      [| yield "--simpleresolution" 
         yield "--noframework" 
         yield "--debug:full" 
         yield "--define:DEBUG" 
         yield "--optimize-" 
         yield "--out:" + dllName
         yield "--warn:3" 
         yield "--fullpaths" 
         yield "--flaterrors" 
         yield "--target:library" 
         yield fileName1
         let references =
           [ sysLib "mscorlib" 
             sysLib "System"
             sysLib "System.Core"
             fsCore4300()
             asm.Location ]
         for r in references do 
               yield "-r:" + r |])
let wholeProjectResults = checker.ParseAndCheckProject(projectOptions) |> Async.RunSynchronously
let refAssemblies = wholeProjectResults.ProjectContext.GetReferencedAssemblies()

let thisAssembly = refAssemblies |> List.find (fun a -> a.SimpleName = asm.GetName().Name)
let thisModule = thisAssembly.Contents.Entities |> Seq.find (fun e -> e.DisplayName = "CompilerServiceStudy")
let piyoClass = thisModule.NestedEntities |> Seq.find (fun e -> e.CompiledName = "Piyo")
let hogeName = piyoClass.DisplayName
> CompilerServiceStudy.hogeName;;
val it : string = "Hoge"

無事"Hoge"が取得できました。ただし内部でコンパイルしているため、初めの1回はすごく遅いです。