
我们经常会遇到这样的事情:有时候我们找到了一个库,但是这个库是用 TypeScript 写的,但是我们想在 C# 调用,于是我们需要设法将原来的 TypeScript 类型声明翻译成 C# 的代码,然后如果是 UI 组件的话,我们需要将其封装到一个 WebVIEw 里面,然后通过 JavaScript 和 C# 的互 *** 作功能来调用该组件的各种方法,支持该组件的各种事件等等。
但是这是一个苦力活,尤其是类型翻译这一步。
这个是我最近在帮助维护一个开源 UWP 项目 monaco-editor-uwp 所需要的,该项目将微软的 monaco 编辑器封装成了 UWP 组件。
然而它的 monaco.d.ts 足足有 1.5 mb,并且 API 经常会变化,如果人工翻译,不仅工作量十分大,还可能会漏掉新的变化,但是如果有一个自动生成器的话,那么人工的工作就会少很多。
目前 GitHub 上面有一个叫做 QuickType 的项目,但是这个项目对 TypeScript 的支持极其有限,仍然停留在 TypeScript 3.2,而且遇到不认识的类型就会报错,比如 DOM 类型等等。
因此我决定手写一个代码生成器 TypedocConverter:https://github.com/hez2010/TypedocConverter
构思本来是打算从 TypeScript 词法和语义分析开始做的,但是发现有一个叫做 Typedoc 的项目已经帮我们完成了这一步,而且支持输出 JsON schema,那么剩下的事情就简单了:我们只需要将 TypeScript 的 AST 转换成 C# 的 AST,然后再将 AST 还原成代码即可。
那么话不多说,这就开写。
构建 Typescipt AST 类型绑定借助于 F# 更加强大的类型系统,类型的声明和使用非常简单,并且具有完善的recursive pattern。pattern matching、option types 等支持,这也是该项目选用 F# 而不是 C# 的原因,虽然 C# 也支持这些,也有一定的 FP 能力,但是它还是偏 OOP,写起来会有很多的样板代码,非常的繁琐。
我们将 Typescipt 的类型绑定定义到 DeFinition.fs 中,这一步直接将 Typedoc 的定义翻译到 F# 即可:
首先是 ReflectionKind 枚举,该枚举表示了 JsON Schema 中各节点的类型:
type ReflectionKind = | Global = 0| ExternalModule = 1| Module = 2| Enum = 4| EnumMember = 16| Variable = 32| Function = 64| Class = 128| Interface = 256| Constructor = 512| Property = 1024| Method = 2048| CallSignature = 4096| IndexSignature = 8192| ConstructorSignature = 16384| Parameter = 32768| Typeliteral = 65536| TypeParameter = 131072| Accessor = 262144| GetSignature = 524288| SetSignature = 1048576| Objectliteral = 2097152| TypeAlias = 4194304| Event = 8388608| Reference = 16777216
然后是类型修饰标志 ReflectionFlags,注意该 record 所有的成员都是 option 的
ReflectionFlags = { IsPrivate: bool option IsProtected: option IsPublic: option Isstatic: option IsExported: option IsExternal: option IsOptional: option Isreset: option HasExportAssignment: option IsConstructorProperty: option IsAbstract: option IsConst: option IsLet: option}
然后到了我们的 Reflection,由于每一种类型的 Reflection 都可以由 ReflectionKind 来区分,因此我选择将所有类型的 Reflection 合并成为一个 record,而不是采用 Union Types,因为后者虽然看上去清晰,但是在实际 parse AST 的时候会需要大量 pattern matching 的代码。
由于部分 records 相互引用,因此我们使用 and 来定义 recursive records。
Reflection = { ID: int name: string Originalname: Kind: ReflectionKind KindString: option Flags: ReflectionFlags Parent: Reflection option Comment: Comment option Sources: SourceReference List option Decorators: Decorator option Decorates: Type List option Url: option Anchor: option HasOwndocument: option CSSClasses: option DefaultValue: option Type: Type option TypeParameter: Reflection List option Signatures: Reflection List option IndexSignature: Reflection List option GetSignature: Reflection List option SetSignature: Reflection List option Overwrites: Type option inheritedFrom: Type option ImplementationOf: Type option ExtendedTypes: Type List option ExtendedBy: Type List option ImplementedTypes: Type List option ImplementedBy: Type List option TypeHIErarchy: DeclarationHIErarchy option Children: Reflection List option Groups: ReflectionGroup List option CategorIEs: Reflectioncategory List option Reflections: Map<,Reflection> option Directory: SourceDirectory option files: Sourcefile List option Readme: option PackageInfo: obj option Parameters: Reflection List option}and DeclarationHIErarchy = { Type: Type List Next: DeclarationHIErarchy option IsTarget: option} Type = { Type: ID: option name: option ElementType: Type option Value: option Types: Type List option TypeArguments: Type List option Constraint: Type option Declaration: Reflection option} Decorator = { name: Type: Type option Arguments: obj option} ReflectionGroup = { Title: Kind: ReflectionKind Children: List CSSClasses: option AllChildrenHaveOwndocument: option AllChildrenAreinherited: option AllChildrenArePrivate: option AllChildrenAreProtectedOrPrivate: option AllChildrenAreExternal: option SomeChildrenAreExported: option CategorIEs: Reflectioncategory List option} Reflectioncategory = { Title: Children: List AllChildrenHaveOwndocument: SourceDirectory = { Parent: SourceDirectory option DirectorIEs: Map< option Dirname: option Url: Sourcefile = { Fullfilename: filename: Url: option Parent: SourceDirectory option Reflections: Reflection List option Groups: ReflectionGroup List option} SourceReference = { file: Sourcefile option filename: line: Character: Comment = { ShortText: Text: option Returns: option Tags: CommentTag List option} CommentTag = { Tagname: Parentname: }
这样,我们就简单的完成了类型绑定的翻译,接下来要做的就是将 Typedoc 生成的 JsON 反序列化成我们所需要的东西即可。
反序列化虽然想着好像一切都很顺利,但是实际上 System.Text.Json、Newtonsoft.JsON 等均不支持 F# 的 option types,所需我们还需要一个 JsonConverter 处理 option types。
本项目采用 Newtonsoft.Json,因为 System.Text.Json 目前尚不成熟。得益于 F# 对 OOP 的兼容,我们可以很容易的实现一个 OptionConverter。
OptionConverter() = inherit JsonConverter() overrIDe __.CanConvert(objectType: Type) : = match objectType.IsGenericType with | false -> false | true -> typedefof<_ option> = objectType.GetGenericTypeDeFinition() overrIDe __.WriteJson(writer: JsonWriter,value: obj,serializer: JsonSerializer) : unit = serializer.Serialize(writer,if isNull value then null else let _,fIElds = FSharpValue.GetUnionFIElds(value,value.GetType()) fIElds.[0] ) __.ReadJson(reader: JsonReader,objectType: Type,_existingValue: obj,serializer: JsonSerializer) : obj = let innerType = objectType.GetGenericArguments().[] value = serializer.Deserialize( reader,1)">if innerType.IsValueType then (typedefof<_ Nullable>).MakeGenericType([|innerType|]) else innerType ) cases = FSharpType.GetUnionCases objectType then FSharpValue.MakeUnion(cases.[0],[||]) else FSharpValue.MakeUnion(cases.[1],[|value|])
这样所有的工作就完成了。
我们可以去 monaco-editor 仓库下载 monaco.d.ts 测试一下我们的 JsON Schema deserializer,可以发现 JsON Sechma 都被正确地反序列化了。
反序列化结果
构建 C# AST 类型
当然,此 "AST" 非彼 AST,我们没有必要其细化到语句层面,因为我们只是要写一个简单的代码生成器,我们只需要构建实体结构即可。
我们将实体结构定义到 Entity.fs 中,在此我们只需支持 interface、class、enum 即可,对于 class 和 interface,我们只需要支持 method、property 和 event 就足够了。
当然,代码中存在泛型的可能,这一点我们也需要考虑。
EntityBodyType = { Type: option InnerTypes: EntityBodyType List} EntityMethod = { Comment: ModifIEr: List Type: EntityBodyType name: TypeParameter: List Parameter: EntityBodyType List} EntityProperty = { Comment: List name: Type: EntityBodyType WithGet: WithSet: IsOptional: InitialValue: option} EntityEvent = { Comment: List DelegateType: EntityBodyType name: } EntityEnum = { Comment: Value: int64 option} EntityType = | Interface| Class| Enum| StringEnum Entity = { namespace: Comment: Methods: EntityMethod List PropertIEs: EntityProperty List Events: EntityEvent List Enums: EntityEnum List inheritedFrom: EntityBodyType List Type: EntityType TypeParameter: List ModifIEr: List}
文档化注释生成器
文档化注释也是少不了的东西,能极大方便开发者后续使用生成的类型绑定,而无需参照原 typescript 类型声明上的注释。
代码很简单,只需要将文本处理成 xml 即可。
let escapeSymbols (text: ) = if isNull text then "" text .Replace("&",&") .Replace(<<>>)let toCommentText (text: else text.Split \n" |> Array.map (fun t -> /// " + escapeSymbols t) |> Array.reduce(fun accu next -> accu + + next) getXmlDocComment (comment: Comment) = let prefix = /// <summary>\n" let suffix = \n/// </summary> summary = match comment.Text with | Some text -> prefix + toCommentText comment.ShortText + toCommentText text + suffix | _ -> match comment.ShortText with | "" -> "" | _ -> prefix + toCommentText comment.ShortText + suffix returns = match comment.Returns with | Some text -> \n/// <returns>\n" + toCommentText text + \n/// </returns>" | _ -> "" summary + returns
类型生成器
Typescript 的类型系统较为灵活,包括 union types、intersect types 等等,这些即使是目前的 C# 8 都不能直接表达,需要等到 C# 9 才行。当然我们可以生成一个 struct 并为其编写隐式转换 *** 作符重载,支持 union types,但是目前尚未实现,我们就先用 union types 中的第一个类型代替,而对于 intersect types,我们姑且先使用 object。
然而 union types 有一个特殊情况:string literals types alias。就是这样的东西:
type Size = "XS" | "S" | "M" | "L" | "XL";即纯 string 值组合的 type alias,这个我们还是有必要支持的,因为在 typescript 中用的非常广泛。
C# 在没有对应语法的时候要怎么支持呢?很简单,我们创建一个 enum,该 enum 包含该类型中的所有元素,然后我们为其编写 JsonConverter,这样就能确保序列化后,typescript 方能正确识别类型,而在 C# 又有 type sound 的编码体验。
另外,我们需要提供一些常用的类型转换:
Array<T> -> T[] Set<T> -> System.Collections.Generic.ISet<T>Map<T> -> System.Collections.Generic.IDictionary<T> Promise<T> -> System.Threading.Tasks.Task<T> callbacks -> System.Func<T...>, System.Action<T...> Tuple 类型其他的数组类型如 Uint32Array 对于 <voID>,我们需要解除泛型,即 T<voID> -> T那么实现如下:
let rec getType (typeInfo: Type): EntityBodyType = genericType = match typeInfo.Type with | intrinsic" -> match typeInfo.name with | Some name -> match name with | number" -> { Type = double; InnerTypes = []; name = None } | booleanboolstringvoID; InnerTypes = []; name = None } | _ -> { Type = object; InnerTypes = []; name = None } | _ -> { Type = ; InnerTypes = []; name = None } | reference" | typeParameterPromiseSystem.Threading.Tasks.TaskSetSystem.Collections.Generic.ISetMapSystem.Collections.Generic.IDictionaryArraySystem.ArrayBigUint64Array"; InnerTypes = [{ Type = ulong; InnerTypes = [ ]; name = None };]; name = None }; | Uint32ArrayuintUint16ArrayushortUint8ArraybyteBigInt64ArraylongInt32ArrayintInt16ArrayshortInt8ArraycharRegExp; InnerTypes = []; name = None }; | x -> { Type = x; InnerTypes = []; name = None }; | _ -> { Type = arraymatch typeInfo.ElementType with | Some elementType -> { Type = ; InnerTypes = [getType elementType]; name = None } | _ -> { Type = ; InnerTypes = []; name = None }]; name = None } | stringliteraltuple" -> match typeInfo.Types with | Some innerTypes -> match innerTypes with | [] -> { Type = System.ValueTuple"; InnerTypes = innerTypes |> List.map getType; name = None } | _ -> { Type = union; InnerTypes = []; name = None } | _ -> printWarning (Taking only the first type " + innerTypes.[0].Type + for the entire union type.) getType innerTypes.[0] // Todo: generate unions| _ ->{ Type = intersection"; InnerTypes = []; name = None } Todo: generate intersections| reflectionmatch typeInfo.Declaration with | Some dec -> match dec.Signatures with | Some [signature] -> paras = match signature.Parameters with | Some p -> p |> List.map (fun pi -> match pi.Type with | Some pt -> Some (getType pt) | _ -> None ) |> List.collect (fun x -> match x with | Some s -> [s] | _ -> [] ) | _ -> [] getDelegateParas (paras: EntityBodyType List): EntityBodyType List = match paras with | [x] -> [{ Type = x.Type; InnerTypes = x.InnerTypes; name = None }] | (front::tails) -> [front] @ getDelegateParas tails | _ -> returnsType = match signature.Type with | Some t -> getType t | _ -> { Type = ; InnerTypes = []; name = None } typeParas = getDelegateParas paras match typeParas with | [] -> { Type = System.Action; InnerTypes = []; name = None } | _ -> if returnsType.Type = " then { Type = ; InnerTypes = typeParas; name = None } else { Type = System.Func; InnerTypes = typeParas @ [returnsType]; name = None } | _ -> { Type = ; InnerTypes = []; name = None } | _ -> { Type = ; InnerTypes = []; name = None } mutable innerTypes = match typeInfo.TypeArguments with | Some args -> getGenericTypeArguments args | _ -> [] if genericType.Type = then with | (front::_) -> if front.Type = " then innerTypes <- [] () | _ -> () () { Type = genericType.Type; name = None; InnerTypes = if innerTypes = [] then genericType.InnerTypes innerTypes; } getGenericTypeArguments (typeInfos: Type List): EntityBodyType List = typeInfos |> List.map getTypeand getGenericTypeParameters (nodes: Reflection List) = Todo: generate constaints types = nodes |> List.where(fun x -> x.Kind = ReflectionKind.TypeParameter) |> List.map ( x.name) types |> List.map (fun x -> {| Type = x; Constraint = "" |})
当然,目前尚不支持生成泛型约束,如果以后有时间的话会考虑添加。
修饰生成器例如 public、private、protected、static 等等。这一步很简单,直接将 ReflectionFlags 转换一下即可,个人觉得使用 mutable 代码会让代码变得非常不优雅,但是有的时候还是需要用一下的,不然会极大地提高代码的复杂度。
getModifIEr (flags: ReflectionFlags) = modifIEr = [] match flags.IsPublic with | Some flag -> if flag then modifIEr <- modifIEr |> List.append [ public" ] () | _ ->match flags.IsAbstract abstractmatch flags.IsPrivate privatematch flags.IsProtected protectedmatch flags.Isstatic static () modifIEr
Enum 生成器
终于到 parse 实体的部分了,我们先从最简单的做起:枚举。 代码很简单,直接将原 AST 中的枚举部分转换一下即可。
let parseEnum (section: ) (node: Reflection): Entity = let values = match node.Children with | Some children -> children |> List.where ( x.Kind = ReflectionKind.EnumMember) | None -> [] { Type = EntityType.Enum; namespace = if section = "" TypeDocgenerator section; ModifIEr = getModifIEr node.Flags; name = node.name Comment = match node.Comment with | Some comment -> getXmlDocComment comment | _ -> Methods = []; PropertIEs = []; Events = []; inheritedFrom = []; Enums = values |> List.map (fun x -> comment = match x.Comment with | Some comment -> getXmlDocComment comment | _ -> "" mutable intValue = 0L match x.DefaultValue with ????? | Some value -> if Int64.TryParse(value,&intValue) then { Comment = comment; name = topascalCase x.name; Value = Some intValue; } match getEnumReferencedValue values value x.name with | Some t -> { Comment = comment; name = x.name; Value = Some (int64 t); } | _ -> { Comment = comment; name = x.name; Value = None; } | _ -> { Comment = comment; name = x.name; Value = None; } ); TypeParameter = [] }
你会注意到一个上面我有一处标了个 ?????,这是在干什么呢?
其实,TypeScript 的 enum 是 recursive 的,也就意味着定义的时候,一个元素可以引用另一个元素,比如这样:
enum MyEnum { A = 1,B = 2,C = A}这个时候,我们需要查找它引用的枚举值,比如在上面的例子里面,处理 C 的时候,需要将它的值 A 用真实值 1 代替。所以我们还需要一个查找函数:
getEnumReferencedValue (nodes: Reflection List) value name = match nodes |> List.where(fun x -> with | Some v -> v <> value && not (name = x.name) | _ -> true ) |> List.where( x.name = value) |> List.tryFind(fun x -> 0 with | Some y -> Int32.TryParse(y,&intValue) | _ -> ) with | Some t -> t.DefaultValue | _ -> None
这样我们的 Enum parser 就完成了。
Interface 和 Class 生成器下面到了重头戏,interface 和 class 才是类型绑定的关键。
我们的函数签名是这样的:
let parseInterfaceAndClass (section: string) (node: Reflection) (isInterface: bool): Entity = ...
首先我们从 Reflection 节点中查找并生成注释、修饰、名称、泛型参数、继承关系、方法、属性和事件:
comment = with | Some comment -> getXmlDocComment comment | _ -> "" exts = (match node.ExtendedTypes with | Some types -> types |> List.map( getType x) | _ -> []) @ (match node.ImplementedTypes []) genericType = types = match node.TypeParameter with | Some tp -> Some (getGenericTypeParameters tp) | _ -> None match types with | Some result -> result | _ -> [] propertIEs = with | Some children -> if isInterface children |> List.where( x.Kind = ReflectionKind.Property) |> List.where(fun x -> x.inheritedFrom = None) exclude inhreited propertIEs |> List.where(fun x -> x.Overwrites = None) exclude overrites propertIEs else children |> List.where( x.Kind = ReflectionKind.Property) | _ -> events = x.Kind = ReflectionKind.Event) |> List.where( exclude inhreited events |> List.where( exclude overrites events x.Kind = ReflectionKind.Event) | _ -> methods = x.Kind = ReflectionKind.Method) |> List.where( exclude inhreited methods |> List.where( exclude overrites methods x.Kind = ReflectionKind.Method) | _ -> []
有一点要注意,就是对于 interface 来说,子 interface 无需重复父 interface 的成员,因此需要排除。
然后我们直接返回一个 record,代表该节点的实体即可。
{ Type = then EntityType.Interface EntityType.Class; namespace = TypedocConverter section; name = node.name; Comment = comment; ModifIEr = getModifIEr node.Flags; inheritedFrom = exts; Methods = methods |> List.map ( fun x -> retType = ( match x.Signatures with | Some signatures -> signatures |> List.where( x.Kind = ReflectionKind.CallSignature) | _ -> []) with | [] -> { Type = ; InnerTypes = []; name = None } | (front::_) -> match front.Type with | Some typeInfo -> getType typeInfo | _ -> { Type = ; InnerTypes = []; name = None } typeParameter = with | Some (sigs::_) -> types = match sigs.TypeParameter with | Some tp -> Some (getGenericTypeParameters tp) | _ -> None with | Some result -> result | _ -> [] | _ -> [] |> List.map ( x.Type) parameters = getmethodParameters (with | Some signatures -> signatures |> List.where( x.Kind = ReflectionKind.CallSignature) |> List.map( match x.Parameters with | Some parameters -> parameters |> List.where(fun p -> p.Kind = ReflectionKind.Parameter) | _ -> [] ) |> List.reduce(fun accu next -> accu @ next) | _ -> []) { Comment = with | Some comment -> getXmlDocComment comment | _ -> ModifIEr = then [] getModifIEr x.Flags; Type = retType name = x.name TypeParameter = typeParameter Parameter = parameters } ); Events = events | paras = with | Some sigs -> sigs |> List.where ( x.Kind = ReflectionKind.Event) |> List.map( x.Parameters) |> List.collect (fun x -> with | Some paras -> paras | _ -> []) | _ -> [] { name = x.name; IsOptional = match x.Flags.IsOptional with | Some optional -> optional | _ -> false ; DelegateType = with | (front::_) -> System.Delegate; name = None; InnerTypes = [] } | _ -> match x.Type ; name = None; InnerTypes = [] } ; Comment = ; ModifIEr = getModifIEr x.Flags; } ); PropertIEs = propertIEs | { Comment = getModifIEr x.Flags; name = x.name Type = with | Some typeInfo -> getType typeInfo | _ -> { Type = ; name = None; InnerTypes = [] } WithGet = ; WithSet = ; IsOptional = ; InitialValue = with | Some value -> Some value | _ -> None } ); Enums = []; TypeParameter = genericType |> List.map( x.Type);}
注意处理 event 的时候,委托的类型需要特殊处理一下。
Type alias 生诚器还记得我们最上面说的一种特殊的 union types 吗?这里就是处理纯 string 的 type alias 的。
let parseUnionTypeAlias (section: ) (node: Reflection) (nodes: Type List): Entity List = let notStringliteral = nodes |> List.tryFind(fun x -> x.Type <> ) enums = match notStringliteral with | Some _ -> printWarning (Type alias " + node.name + is not supported.) [] | None -> nodes |> List.collect (fun x -> match x.Value with | Some value -> [{ name = topascalCase value Comment = ///<summary>\n" + toCommentText value + \n///</summary> Value = None }] | _ -> [] ) if enums = [] [ { namespace = section name = node.name Comment = with | Some comment -> getXmlDocComment comment | _ -> Methods = [] Events = [] PropertIEs = [] Enums = enums inheritedFrom = [] Type = EntityType.StringEnum TypeParameter = [] ModifIEr = getModifIEr node.Flags } ]let parseTypeAlias (section: ) (node: Reflection): Entity List = typeInfo = node.Type match typeInfo with | Some aliasType -> match aliasType.Type match aliasType.Types with | Some types -> parseUnionTypeAlias section node types | _ -> printWarning () [] | _ -> printWarning () [] | _ -> []
组合 Prasers
我们最后将以上 parsers 组合起来就 ojbk 了:
rec parseNode (section: match node.Kind with | ReflectionKind.Global -> with | Some children -> parseNodes section children | _ -> [] | ReflectionKind.Module -> parseNodes (then node.name else section + . + node.name) children | _ -> [] | ReflectionKind.ExternalModule -> [] | ReflectionKind.Enum -> [parseEnum section node] | ReflectionKind.Interface -> [parseInterfaceAndClass section node ] | ReflectionKind.Class -> [parseInterfaceAndClass section node ] | ReflectionKind.TypeAlias -> match node.Type parseTypeAlias section node | _ -> [] | _ -> [] parseNodes section (nodes: Reflection List): Entity List = match nodes with | ([ front ]) -> parseNode section front | (front :: tails) -> parseNode section front @ parseNodes section tails | _ -> []
至此,我们的 parse 工作全部搞定,完结撒花~~~
代码生成有了 C# 的实体类型,代码生成还困难吗?
不过有一点要注意的是,我们需要将名称转换为 Pascal Case,还需要生成 string literals union types 的 JsonConverter。不过这些都是样板代码,非常简单。
这里就不放代码了,感兴趣的同学可以自行去我的 GitHub 仓库查看。
测试效果原 typescipt 代码:
declare namespace test { /** * The declaration of an enum */ export enum MyEnum { A = 0= 2 C } * * The declaration of an interface export interface MyInterface1 { * * A method testMethod(arg: string,callback: () => voID): string; * * An event * @event onTest(Listener: (e: MyInterface1) => voID): ; * * An property Readonly testProp: string; } * * Another declaration of an interface export interface MyInterface2<T> { testMethod(arg: T,1)">): T; onTest(Listener: (e: MyInterface2<T>) => Readonly testProp: T; } * * The declaration of a class export class MyClass1<T> implements MyInterface1 { Readonly testProp: string; static staticmethod(value: string,isOption?: boolean): UnionStr; } * * Another declaration of a class export class MyClass2<T> implements MyInterface2<T> Readonly testProp: T; static staticmethod(value: string,1)">* * The declaration of a type alias export type UnionStr = "A" | "B" | "C" | "other";}
Typedoc 生成的 JsON 后,将其作为输入,生成 C# 代码:
namespace TypedocConverter.Test{ /// <summary> /// The declaration of an enum </summary> enum MyEnum { [Newtonsoft.Json.JsonProperty(A Newtonsoft.Json.NullValueHandling.Ignore)] A = B Newtonsoft.Json.NullValueHandling.Ignore)] B = 1C Newtonsoft.Json.NullValueHandling.Ignore)] C = 2D Newtonsoft.Json.NullValueHandling.Ignore)] D = }} The declaration of a class class MyClass1<T> : MyInterface1 { <summary> An property </summary> [Newtonsoft.Json.JsonProperty(testProp Newtonsoft.Json.NullValueHandling.Ignore)] string TestProp { get => throw new System.NotImplementedException(); set => new System.NotImplementedException(); } event System.Action<MyInterface1> OnTest; string TestMethod(string arg,System.Action callback) => System.NotImplementedException(); static UnionStr Staticmethod(string value,bool isOption) => System.NotImplementedException(); }} Another declaration of a class class MyClass2<T> : MyInterface2<T> { Newtonsoft.Json.NullValueHandling.Ignore)] T TestProp { event System.Action<MyInterface2<T>> OnTest; T TestMethod(T arg,System.Action callback) => The declaration of an interface interface MyInterface1 { get; set; } arg,System.Action callback); }} Another declaration of an interface interface MyInterface2<T> The declaration of a type alias </summary> [Newtonsoft.Json.JsonConverter(typeof(UnionStrConverter))] UnionStr { /// A Newtonsoft.Json.NullValueHandling.Ignore)] A, B Newtonsoft.Json.NullValueHandling.Ignore)] B,1)"> C Newtonsoft.Json.NullValueHandling.Ignore)] C,1)"> other Other Newtonsoft.Json.NullValueHandling.Ignore)] Other } class UnionStrConverter : Newtonsoft.Json.JsonConverter { public overrIDe bool CanConvert(System.Type t) => t == typeof(UnionStr) || t == typeof(UnionStr?); object ReadJson(Newtonsoft.Json.JsonReader reader,System.Type t,1)">object? existingValue,Newtonsoft.Json.JsonSerializer serializer) => reader.TokenType switch { Newtonsoft.Json.JsonToken.String => serializer.Deserialize<string>(reader) { " => UnionStr.A,1)"> UnionStr.B,1)"> UnionStr.C,1)"> UnionStr.Other,_ => new System.Exception(Cannot unmarshal type UnionStr) },1)">) }; voID WriteJson(Newtonsoft.Json.JsonWriter writer,1)"> untypedValue,Newtonsoft.Json.JsonSerializer serializer) { if (untypedValue is null) { serializer.Serialize(writer,1)">null); return; } var value = (UnionStr)untypedValue; (value) { case UnionStr.A: serializer.Serialize(writer,1)">"); ; case UnionStr.B: serializer.Serialize(writer,1)">case UnionStr.C: serializer.Serialize(writer,1)">case UnionStr.Other: serializer.Serialize(writer,1)">default: break; } Cannot marshal type UnionStr); } }}
后记
有了这个工具后,妈妈再也不用担心我封装 TypeScript 的库了。有了 TypedocConverter,任何 TypeScript 的库都能轻而易举地转换成 C# 的类型绑定,然后进行封装,非常方便。
感谢大家看到这里,最后,欢迎大家使用 TypedocConverter。当然,如果能 star 一波甚至贡献代码,我会非常感谢的!
总结以上是内存溢出为你收集整理的用 F# 手写 TypeScript 转 C# 类型绑定生成器全部内容,希望文章能够帮你解决用 F# 手写 TypeScript 转 C# 类型绑定生成器所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)