CodeDOM -- generating an interface to a type
This is a fairly simple process -- reflect over the public properties and methods, and generate a matching interface member, rather like this:
let GenerateInterface (target:Type) = | |
let compileUnit = new CodeCompileUnit() | |
let scope = new CodeNamespace("Generated") | |
let namespaces = new HashSet<string>() | |
compileUnit.Namespaces.Add( scope ) |> ignore | |
let wrapper = new CodeTypeDeclaration("I" + target.Name + "Wrapper") | |
wrapper.IsInterface <- true | |
scope.Types.Add(wrapper) |> ignore | |
IncludeProperties wrapper target namespaces | |
IncludeMethods wrapper target namespaces | |
namespaces | |
|> Seq.sort | |
|> Seq.iter (fun n-> scope.Imports.Add(new CodeNamespaceImport(n))) | |
compileUnit |
which puts the required namespaces into order (TODO: sort System.*
ahead of the rest).
The two functions look much the same, so could be factored further:
let IncludeProperties (klass:CodeTypeDeclaration) (target:Type) (namespaces:HashSet<string>) = | |
target.GetProperties(BindingFlags.Instance ||| BindingFlags.Public) | |
|> Seq.sortBy (fun p -> p.Name) | |
|> Seq.iter (fun p -> | |
let m = new CodeMemberProperty() | |
m.Name <- p.Name | |
namespaces.Add(p.PropertyType.Namespace) |> ignore | |
m.Type <- TypeRef(p.PropertyType) | |
m.HasGet <- p.CanRead | |
m.HasSet <- p.CanWrite | |
p.GetIndexParameters() | |
|> Seq.iter ( fun t -> | |
let px = new CodeParameterDeclarationExpression() | |
px.Direction <- FieldDirection.In | |
px.Name <- t.Name | |
namespaces.Add(t.ParameterType.Namespace) |> ignore | |
px.Type <- TypeRef(t.ParameterType) | |
m.Parameters.Add(px) |> ignore | |
) | |
if m.Parameters.Count > 0 then m.Name <- "Item" | |
klass.Members.Add( m ) |> ignore | |
) |
which special cases the indexer property; and
let IncludeMethods (klass:CodeTypeDeclaration) (target:Type) (namespaces:HashSet<string>) = | |
target.GetMethods(BindingFlags.Instance ||| BindingFlags.Public) | |
|> Seq.filter (fun x -> not x.IsSpecialName ) | |
|> Seq.sortBy (fun p -> p.Name) | |
|> Seq.iter (fun p -> | |
let m = new CodeMemberMethod() | |
m.Name <- p.Name | |
m.ReturnType <- TypeRef(p.ReturnType) | |
namespaces.Add(p.ReturnType.Namespace) |> ignore | |
p.GetParameters() | |
|> Seq.iter ( fun (t:ParameterInfo) -> | |
let px = new CodeParameterDeclarationExpression() | |
px.Direction <- if t.IsIn && t.IsOut then FieldDirection.Ref | |
else if t.IsOut then FieldDirection.Out | |
else FieldDirection.In | |
px.Name <- t.Name | |
namespaces.Add(t.ParameterType.Namespace) |> ignore | |
px.Type <- TypeRef(t.ParameterType) | |
m.Parameters.Add(px) |> ignore | |
) | |
klass.Members.Add( m ) |> ignore | |
) |
(TODO: generics and attributes, which I don't need for my immediate use case)
To make the code look less cluttered, to get type specifications which aren't fully namespace qualified everywhere, we want to specify types with keyword synonyms by type object, and the rest by name (with the namespaces included by using
or open
). So we have helper functions
let SysType (t:Type) = | |
if t = typeof<System.Object> || | |
t = typeof<System.Char> || | |
t = typeof<System.Boolean> || | |
t = typeof<System.String> || | |
t = typeof<System.Int32> || | |
// also uint, float, etc... ; probably also Nullables | |
t = typeof<System.Void> then true | |
else false | |
let TypeRef (t:Type) = | |
if t.IsArray then | |
if SysType <| t.GetElementType() then new CodeTypeReference(t) | |
else new CodeTypeReference(t.Name) | |
else if SysType t then new CodeTypeReference(t) | |
else new CodeTypeReference(t.Name) |
where again, TODO handling generic types, especially collections, to get them looking pretty.
So if we use string
as the type, and dump to C# we get
namespace Generated { | |
using System; | |
using System.Globalization; | |
using System.Text; | |
public interface IStringWrapper { | |
char this[int index] { | |
get; | |
} | |
int Length { | |
get; | |
} | |
object Clone(); | |
... | |
string TrimStart(char[] trimChars); | |
} | |
} |
and in F#
namespace Generated | |
// Generated by F# CodeDom stuff omitted... | |
open System | |
open System.Globalization | |
open System.Text | |
type | |
IStringWrapper = interface | |
abstract Item : int -> char with get | |
abstract Length : int with get | |
abstract TrimStart : char[] -> string | |
... | |
abstract Clone : unit -> obj | |
end |
where interestingly, the sorted order of the inserted members is reversed.
No comments :
Post a Comment