-
Couldn't load subscription status.
- Fork 4
Immutable dynamic object #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
8a040f7
Immutable dynamic object
WhiteBlackGoose c771d73
Operator for With, TryGetTypedValue added
WhiteBlackGoose d741cd8
Docs and tests added
WhiteBlackGoose d29b7ce
Type conservation added
WhiteBlackGoose 6730971
Attempt
WhiteBlackGoose b6c9255
F# and C# now work
WhiteBlackGoose 57e253e
properties immutable
WhiteBlackGoose 690f14b
Type preservation test
WhiteBlackGoose d596876
wrong comment
WhiteBlackGoose 76c0d3c
Ugly named functions are now internal
WhiteBlackGoose b6e5d75
A reingeneered approach for type preservation
WhiteBlackGoose 4cef787
Minor refactorings
WhiteBlackGoose 459a227
Fields preservation
WhiteBlackGoose c83cf62
API changed
WhiteBlackGoose f43e072
Operators moved to a separate module
WhiteBlackGoose 1187cb9
Added addOpt and addOptBy
WhiteBlackGoose File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| namespace DynamicObj | ||
|
|
||
| open DynamicObj | ||
| open System.Reflection | ||
| open System.Runtime.CompilerServices | ||
|
|
||
| [<InternalsVisibleToAttribute("UnitTests")>] | ||
| do() | ||
|
|
||
| /// Represents an DynamicObj's counterpart | ||
| /// with immutability enabled only. | ||
| type ImmutableDynamicObj internal (map : Map<string, obj>) = | ||
|
|
||
| let mutable properties = map | ||
|
|
||
| member private this.Properties | ||
| with get () = | ||
| properties | ||
| and set value = | ||
| properties <- value | ||
|
|
||
| // Copies the fields of one object to the other one | ||
| // If their base is not ImmutableDynamicObj, then it | ||
| // will go over fields from the base instance | ||
| static member private copyMembers (ty : System.Type) sourceObject destinationObject = | ||
| for fi in ty.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic ||| BindingFlags.Public) do | ||
| let fieldValue = fi.GetValue sourceObject | ||
| fi.SetValue(destinationObject, fieldValue) | ||
|
|
||
| static member private newIfNeeded (object : 'a when 'a :> ImmutableDynamicObj) map : 'a = | ||
| if obj.ReferenceEquals(map, object.Properties) then | ||
| object | ||
| else | ||
| // otherwise we create a new instance | ||
| let res = new 'a() | ||
|
|
||
| // and then copy all current fields the new instance | ||
| ImmutableDynamicObj.copyMembers (typeof<'a>) object res | ||
| res.Properties <- map | ||
| res | ||
|
|
||
| /// Empty instance | ||
| new () = ImmutableDynamicObj Map.empty | ||
|
|
||
| static member empty = ImmutableDynamicObj () | ||
|
|
||
| /// Indexes ; if no key found, throws | ||
| member this.Item | ||
| with get(index) = | ||
| this.Properties.[index] | ||
|
|
||
| /// Returns an instance with: | ||
| /// 1. this property added if it wasn't present | ||
| /// 2. this property updated otherwise | ||
| static member add name newValue (object : #ImmutableDynamicObj) = | ||
| object.Properties | ||
| |> Map.add name newValue | ||
| |> ImmutableDynamicObj.newIfNeeded object | ||
|
|
||
| /// Returns an instance: | ||
| /// 1. the same if there was no requested property | ||
| /// 2. without the requested property if there was | ||
| static member remove name (object : #ImmutableDynamicObj) = | ||
| object.Properties | ||
| |> Map.remove name | ||
| |> ImmutableDynamicObj.newIfNeeded object | ||
|
|
||
| member this.TryGetValue name = | ||
| match this.Properties.TryGetValue name with | ||
| | true, value -> Some value | ||
| | _ -> ReflectionUtils.tryGetPropertyValue this name | ||
|
|
||
| member this.TryGetTypedValue<'a> name = | ||
| match this.TryGetValue name with | ||
| | None -> None | ||
| | Some o -> | ||
| match o with | ||
| | :? 'a as o -> o |> Some | ||
| | _ -> None | ||
|
|
||
| override this.Equals o = | ||
| match o with | ||
| | :? ImmutableDynamicObj as other -> other.Properties = this.Properties | ||
| | _ -> false | ||
|
|
||
| override this.GetHashCode () = ~~~map.GetHashCode() | ||
|
|
||
| [<Extension>] | ||
| type ImmutableDynamicObjExtensions = | ||
|
|
||
| /// Returns an instance with: | ||
| /// 1. this property added if it wasn't present | ||
| /// 2. this property updated otherwise | ||
| /// use this one only from C# | ||
| [<Extension>] | ||
| static member AddItem (this, name, newValue) = | ||
| ImmutableDynamicObj.add name newValue this | ||
|
|
||
| /// Returns an instance: | ||
| /// 1. the same if there was no requested property | ||
| /// 2. without the requested property if there was | ||
| /// use this one only from C# | ||
| [<Extension>] | ||
| static member RemoveItem (this, name) = | ||
| ImmutableDynamicObj.remove name this |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| module DynamicObj.Operators | ||
|
|
||
|
|
||
|
|
||
| /// Returns an instance with: | ||
| /// 1. this property added if it wasn't present | ||
| /// 2. this property updated otherwise | ||
| let (++) object (name, newValue) = ImmutableDynamicObj.add name newValue object | ||
|
|
||
|
|
||
| /// Returns an instance: | ||
| /// 1. the same if there was no requested property | ||
| /// 2. without the requested property if there was | ||
| let (--) object name = ImmutableDynamicObj.remove name object | ||
|
|
||
|
|
||
| /// Acts as (++) if the value is Some, | ||
| /// returns the same object otherwise | ||
| let (++?) object (name, newValue) = | ||
WhiteBlackGoose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| match newValue with | ||
| | Some(value) -> object ++ (name, value) | ||
| | None -> object | ||
|
|
||
| /// Acts as (++?) but maps the valid value | ||
| /// through the last argument | ||
| let (++??) object (name, newValue, f) = | ||
WhiteBlackGoose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| match newValue with | ||
| | Some(value) -> object ++ (name, f value) | ||
| | None -> object | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net5.0</TargetFramework> | ||
|
|
||
| <IsPackable>false</IsPackable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> | ||
| <PackageReference Include="xunit" Version="2.4.1" /> | ||
| <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | ||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
| <PrivateAssets>all</PrivateAssets> | ||
| </PackageReference> | ||
| <PackageReference Include="coverlet.collector" Version="3.0.2"> | ||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
| <PrivateAssets>all</PrivateAssets> | ||
| </PackageReference> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\DynamicObj\DynamicObj.fsproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| using System; | ||
| using Xunit; | ||
| using DynamicObj; | ||
| using Microsoft.FSharp.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
|
|
||
| namespace CSharpTests | ||
| { | ||
| public class InteropWorks | ||
| { | ||
| [Fact] | ||
| public void Test1() | ||
| { | ||
| var obj1 = | ||
| new ImmutableDynamicObj() | ||
| .AddItem("aa", 4) | ||
| .AddItem("bb", 10) | ||
| .RemoveItem("aa") | ||
| ; | ||
| var obj2 = | ||
| new ImmutableDynamicObj() | ||
| .AddItem("bb", 10) | ||
| ; | ||
| Assert.Equal(obj1, obj2); | ||
| } | ||
|
|
||
| public class MyDynamicObject : ImmutableDynamicObj | ||
| { | ||
|
|
||
| } | ||
|
|
||
| [Fact] | ||
| public void Test2() | ||
| { | ||
| var obj1 = | ||
| new MyDynamicObject() | ||
| .AddItem("aaa", 5); | ||
| Assert.IsType<MyDynamicObject>(obj1); | ||
| Assert.Equal(5, obj1["aaa"]); | ||
| } | ||
|
|
||
| public class MyDynamicObjectWithField : ImmutableDynamicObj | ||
| { | ||
| public int Aaa { get; init; } | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TestFieldsPreservedForInheritor() | ||
| { | ||
| var obj1 = | ||
| new MyDynamicObjectWithField() { Aaa = 100500 } | ||
| .AddItem("aaa", 5) | ||
| .RemoveItem("aaa"); | ||
| Assert.Equal(100500, obj1.Aaa); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if this has any effect, but this is from VS Preview right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I will revert it once we understand that everything else is good enough