Test and coverage badges are the last green CI run on master. CI fails the PR if any test is skipped, line coverage drops below 77%, or branch coverage drops below 100%.
F# operators for the Dynamic Language Runtime. target?Name, target?Name <- value, and !?target are the F# spelling of C# dynamic, with piping and 'T option lookups.
Docs: fsprojects.github.io/FSharp.Interop.Dynamic
This library sits on Dynamitey 3.0.3. It is the F# surface, not Dynamitey itself.
F# has no dynamic keyword. Use this when the member is not known at compile time: Expando/JSON bags, optional fields, method names from config, C# APIs that return dynamic, pythonnet, COM. Use ordinary F# when the type is in your project. Full argument: Why this library.
6.0.0 is the current package: netstandard2.0 + net10.0, Dyn.tryGet / Dyn.exists. That is a TFM break from 5.0.1.268 (net45 / netstandard1.6 / netstandard2.0). netstandard2.0 still covers current .NET Framework and .NET.
dotnet add package FSharp.Interop.Dynamicopen FSharp.Interop.Dynamic
open FSharp.Interop.Dynamic.Operators // optional: ?+?, ?=?, …open System.Dynamic
open FSharp.Interop.Dynamic
let o = ExpandoObject()
o?Name <- "Ada"
let name: string = o?Name
let hello: string = "HelloWorld"?Substring(0, 5)Annotate the result type when F# cannot infer it. Void CLR methods need unit:
let items = ResizeArray<string>()
let _: unit = items?Add("x")let present: string option = o |> Dyn.tryGet "Name" // Some "Ada"
let missing: string option = o |> Dyn.tryGet "NoSuch" // None
o |> Dyn.exists "Name" // true
o |> Dyn.exists "NoSuch" // falseLookup is Dynamitey InvokeGet. A present null is still present. A present value that cannot convert to 'T still throws — that is not a miss.
o |> Dyn.set "Name" "Ada"
let name: string = o |> Dyn.get "Name"
let hello: string = "HelloWorld" |> Dyn.invokeMember "Substring" (0, 5)Target is last so piping works.
open FSharp.Interop.Dynamic.Operators
let n: int = 5 ?+? 4
let f: float = 5 ?+? 3.5
let s: string = "Hello" ?+? " World"
[1; 2; 3; 4] |> List.reduce (?+?)let add3: int -> int = !?(+) 3
add3 4 // 7| You write | DLR |
|---|---|
target?Name when 'T is not a function |
InvokeGet |
target?Name when 'T is a function, then apply |
InvokeMember |
target?Name <- value |
InvokeSet |
!?target |
Invoke on the target itself |
That is why target?Foo(1, 2) is a method call: application forces a function type.
6.0.0 references Dynamitey 3.0.3. tryGet / exists do not wait on Dynamitey 4.0.0. The community continuation of Dynamitey is dynamitey-community/dynamitey; switching this package to it is #29.
You can still open Dynamitey for Build, Dynamic.Curry, DynamicObjects.Dictionary.
- The DLR cannot see explicit interface members (same as C#
dynamic). - Not trim-safe or NativeAOT-safe.
- Do not build member names from untrusted input. SECURITY.md.
- Historical: .NET Core 2.0.0–2.0.2 broke
dynamicon nested types inside generics (#11). Current TFMs are fine.
Full list: Caveats.
Every F# snippet above is a test in Tests/ReadmeExamples.fs. pythonnet and SignalR sketches live on the why page and are not compiled here.
Requires the .NET 10 SDK.
dotnet restore
dotnet build -c Release -warnaserror
dotnet test --project Tests/Tests.fsproj -c ReleaseDocs: dotnet tool install -g docfx --version 2.78.5, then dotnet build FSharp.Interop.Dynamic/FSharp.Interop.Dynamic.fsproj -c Release && docfx docfx/docfx.json.
Release: add the version's section to CHANGELOG.md, then tag v6.0.0 and push. Releasing.
- Bugs and feature requests: open an issue. Include the F# and .NET versions and a minimal snippet.
- Security problems: do not open a public issue. Follow SECURITY.md.
- Code and docs: pull requests are welcome. CONTRIBUTING.md covers the process and what a PR needs before it merges.
fsprojects default: @fsprojectsgit.