├── .assets ├── nupkg-icon.docx └── nupkg-icon.png ├── .github └── workflows │ └── unit-tests.yml ├── .gitignore ├── DynamicBinder.Test ├── DynamicBinder.Test.csproj ├── DynamicBinderTest.cs ├── LateBinderTest.cs └── TestTargetClass.cs ├── DynamicBinder.sln ├── DynamicBinder ├── Binder.cs ├── DynamicBinder.cs ├── DynamicBinder.csproj ├── DynamicBinderExtension.cs └── LateBinder.cs ├── LICENSE ├── README.md ├── RELEASE-NOTES.txt └── _dist └── .gitkeep /.assets/nupkg-icon.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/dynamicbinder/88208c45e2e5013dec3c8417c2828118338ff813/.assets/nupkg-icon.docx -------------------------------------------------------------------------------- /.assets/nupkg-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/dynamicbinder/88208c45e2e5013dec3c8417c2828118338ff813/.assets/nupkg-icon.png -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: unit tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # Checkout the code 12 | - uses: actions/checkout@v3 13 | 14 | # Install .NET SDK 15 | - name: Setup .NET SDK 16 | uses: actions/setup-dotnet@v2 17 | with: 18 | dotnet-version: 8.0.* 19 | 20 | # Perform unit tests 21 | - name: Perform unit tests 22 | run: dotnet test DynamicBinder.Test --nologo 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | /packages/ 4 | /TestResult/ 5 | bin/ 6 | obj/ 7 | .vs/ 8 | _dist/*.nupkg -------------------------------------------------------------------------------- /DynamicBinder.Test/DynamicBinder.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | DynamicBinderTest 6 | false 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DynamicBinder.Test/DynamicBinderTest.cs: -------------------------------------------------------------------------------- 1 | using Toolbelt; 2 | using Toolbelt.DynamicBinderExtension; 3 | using Xunit; 4 | 5 | namespace DynamicBinderTest; 6 | 7 | public class DynamicBinderTest 8 | { 9 | [Fact] 10 | public void CreateInstanceT_for_public_constructor() 11 | { 12 | var obj = DynamicBinder.CreateInstance(); 13 | ((string)obj.PropA).Is("Fizz"); 14 | ((DateTime)obj._FieldB).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 15 | } 16 | 17 | [Fact] 18 | public void CreateInstanceT_for_private_constructor_1() 19 | { 20 | var obj = DynamicBinder.CreateInstance("Lorem"); 21 | ((string)obj.PropA).Is("Lorem"); 22 | ((DateTime)obj._FieldB).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 23 | } 24 | 25 | [Fact] 26 | public void CreateInstanceT_for_private_constructor_2() 27 | { 28 | var obj = DynamicBinder.CreateInstance("Ipsum", DateTime.Parse("2022/03/27 15:49:10")); 29 | ((string)obj.PropA).Is("Ipsum"); 30 | ((DateTime)obj._FieldB).ToString("yyyy/MM/dd HH:mm:ss").Is("2022/03/27 15:49:10"); 31 | } 32 | 33 | [Fact] 34 | public void CreateInstance_for_public_constructor() 35 | { 36 | var obj = DynamicBinder.CreateInstance(typeof(TestTargetClass)); 37 | ((string)obj.PropA).Is("Fizz"); 38 | ((DateTime)obj._FieldB).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 39 | } 40 | 41 | [Fact] 42 | public void CreateInstance_for_private_constructor_1() 43 | { 44 | var obj = DynamicBinder.CreateInstance(typeof(TestTargetClass), "Lorem"); 45 | ((string)obj.PropA).Is("Lorem"); 46 | ((DateTime)obj._FieldB).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 47 | } 48 | 49 | [Fact] 50 | public void CreateInstance_for_private_constructor_2() 51 | { 52 | var obj = DynamicBinder.CreateInstance(typeof(TestTargetClass), "Ipsum", DateTime.Parse("2022/03/27 15:49:10")); 53 | ((string)obj.PropA).Is("Ipsum"); 54 | ((DateTime)obj._FieldB).ToString("yyyy/MM/dd HH:mm:ss").Is("2022/03/27 15:49:10"); 55 | } 56 | 57 | [Fact] 58 | public void CreateInstance_with_DynamicObject_by_Dynamic() 59 | { 60 | // Given 61 | var testTarget = DynamicBinder.CreateInstance(); 62 | var subItemA = testTarget.CreateSubItem1(); 63 | 64 | // When 65 | var subItemB = DynamicBinder.CreateInstance(subItemA); 66 | 67 | // Then 68 | ((string)subItemB.Name).Is("Jude"); 69 | ((int)subItemB.Value).Is(47); 70 | } 71 | 72 | [Fact] 73 | public void CallOverloadedPrivateInstanceMethod_by_Dynamic() 74 | { 75 | object obj = new TestTargetClass(); 76 | 77 | var actual1 = (string)obj.ToDynamic().MethodC("King"); 78 | actual1.Is("Method-C by string: King"); 79 | 80 | var actual2 = (int)obj.ToDynamic().MethodC(29); 81 | actual2.Is(29); 82 | } 83 | 84 | [Fact] 85 | public void GetAndSetPrivateInstanceProperty_by_Dynamic() 86 | { 87 | object obj = new TestTargetClass(); 88 | var actual1 = (string)obj.ToDynamic().PropA; 89 | actual1.Is("Fizz"); 90 | 91 | obj.ToDynamic().PropA = "Dynamic Buzz"; 92 | 93 | var actual2 = (string)obj.ToDynamic().PropA; 94 | actual2.Is("Dynamic Buzz"); 95 | } 96 | 97 | [Fact] 98 | public void GetAndSetPrivateInstanceField_by_Dynamic() 99 | { 100 | object obj = new TestTargetClass(); 101 | var actual1 = (DateTime)obj.ToDynamic()._FieldB; 102 | actual1.Is(DateTime.Parse("2014/02/13 14:27:56")); 103 | 104 | obj.ToDynamic()._FieldB = DateTime.Parse("2016/12/10 03:07:04"); 105 | 106 | var actual2 = (DateTime)obj.ToDynamic()._FieldB; 107 | actual2.Is(DateTime.Parse("2016/12/10 03:07:04")); 108 | } 109 | 110 | [Fact] 111 | public void CallOverloadedPrivateStaticMethod_by_Dynamic() 112 | { 113 | var binder = DynamicBinder.Create(); 114 | 115 | var actual1 = (string)binder.MethodF("Emperor", 46); 116 | actual1.Is("Method-F(int): Emperor / 46"); 117 | 118 | var actual2 = (string)binder.MethodF("Strap", 19.28); 119 | actual2.Is("Method-F(double): Strap / 19.28"); 120 | } 121 | 122 | [Fact] 123 | public void GetAndSetPrivateStaticProperty_by_Dynamic() 124 | { 125 | var binder = DynamicBinder.Create(typeof(TestTargetClass)); 126 | var actual1 = (string)binder.PropD; 127 | actual1.IsNull(); 128 | 129 | try 130 | { 131 | binder.PropD = "Dynamic FizzBuzz"; 132 | 133 | var actual2 = (string)binder.PropD; 134 | actual2.Is("Dynamic FizzBuzz"); 135 | } 136 | finally 137 | { 138 | binder.PropD = null; 139 | } 140 | } 141 | 142 | [Fact] 143 | public void GetAndSetPrivateStaticField_by_Dynamic() 144 | { 145 | var obj = new TestTargetClass(); 146 | var binder = DynamicBinder.Create(obj.GetType()); 147 | var actual1 = (string)binder._FieldE; 148 | actual1.Is("Static Foo"); 149 | 150 | try 151 | { 152 | binder._FieldE = "Static Dynamic Bar"; 153 | 154 | var actual2 = (string)binder._FieldE; 155 | actual2.Is("Static Dynamic Bar"); 156 | } 157 | finally 158 | { 159 | binder._FieldE = "Static Foo"; 160 | } 161 | } 162 | 163 | [Fact] 164 | public void RetrieveClassObject_by_Dynamic() 165 | { 166 | var binder = DynamicBinder.Create(); 167 | 168 | var subItem = binder.GetSubItem(); 169 | ((string)subItem.Name).Is("John"); 170 | ((int)subItem.Value).Is(40); 171 | } 172 | 173 | [Fact] 174 | public void GetAndSetPropOfClassObject_by_Dynamic() 175 | { 176 | object obj = new TestTargetClass(); 177 | string name1 = obj.ToDynamic().PropH.Name; 178 | int value1 = obj.ToDynamic().PropH.Value; 179 | name1.Is("Alice"); 180 | value1.Is(29); 181 | 182 | obj.ToDynamic().PropH.Name = "Bob"; 183 | obj.ToDynamic().PropH.Value = 37; 184 | string name2 = obj.ToDynamic().PropH.Name; 185 | int value2 = obj.ToDynamic().PropH.Value; 186 | name2.Is("Bob"); 187 | value2.Is(37); 188 | 189 | string name3 = obj.ToDynamic().PropG.Name; 190 | int value3 = obj.ToDynamic().PropG.Value; 191 | name3.Is("Sam"); 192 | value3.Is(33); 193 | 194 | obj.ToDynamic().PropG = new TestTargetClass.SubItemClass1("Baby", 0); 195 | string name4 = obj.ToDynamic().PropG.Name; 196 | int value4 = obj.ToDynamic().PropG.Value; 197 | name4.Is("Baby"); 198 | value4.Is(0); 199 | } 200 | 201 | public enum Gender { Male, Female } 202 | 203 | [Fact] 204 | public void GetPropOfAnonymousType_by_Dynamic() 205 | { 206 | object obj = new 207 | { 208 | Person = new 209 | { 210 | Gender = Gender.Male, 211 | Birthday = DateTime.Parse("1970/01/15"), 212 | ProgramingLangs = new[] { "C#", "F#" } 213 | }, 214 | Count = 1 215 | }; 216 | 217 | Gender gender = obj.ToDynamic().Person.Gender; 218 | DateTime birthday = obj.ToDynamic().Person.Birthday; 219 | int birthdayYear = obj.ToDynamic().Person.Birthday.Year; 220 | string birthdayMonth = obj.ToDynamic().Person.Birthday.ToLocalTime().Month.ToString(); 221 | string[] programingLangs = obj.ToDynamic().Person.ProgramingLangs; 222 | int count = obj.ToDynamic().Count; 223 | 224 | gender.Is(Gender.Male); 225 | birthday.Is(DateTime.Parse("1970/01/15")); 226 | birthdayYear.Is(1970); 227 | birthdayMonth.Is("1"); 228 | programingLangs.Is("C#", "F#"); 229 | count.Is(1); 230 | } 231 | 232 | [Fact] 233 | public void RetrieveReturnValueOfMethod() 234 | { 235 | object obj = new TestTargetClass(); 236 | var retval = obj.ToDynamic().CreateSubItem1() as DynamicBinder; 237 | retval.Object.ToString().Is("Good Job!"); 238 | } 239 | 240 | // ------------------------- 241 | 242 | [Fact] 243 | public void CallOverloadedPrivateInstanceMethod_of_DerivedClass_by_Dynamic() 244 | { 245 | object obj = new DerivedTestTargetClass(); 246 | 247 | var actual1 = (string)obj.ToDynamic().MethodC("King"); 248 | actual1.Is("Method-C by string: King"); 249 | 250 | var actual2 = (int)obj.ToDynamic().MethodC(29); 251 | actual2.Is(29); 252 | } 253 | 254 | [Fact] 255 | public void GetAndSetPrivateInstanceProperty_of_DerivedClass_by_Dynamic() 256 | { 257 | object obj = new DerivedTestTargetClass(); 258 | var actual1 = (string)obj.ToDynamic().PropA; 259 | actual1.Is("Fizz"); 260 | 261 | obj.ToDynamic().PropA = "Dynamic Buzz"; 262 | 263 | var actual2 = (string)obj.ToDynamic().PropA; 264 | actual2.Is("Dynamic Buzz"); 265 | } 266 | 267 | [Fact] 268 | public void GetAndSetPrivateInstanceField_of_DerivedClass_by_Dynamic() 269 | { 270 | object obj = new DerivedTestTargetClass(); 271 | var actual1 = (DateTime)obj.ToDynamic()._FieldB; 272 | actual1.Is(DateTime.Parse("2014/02/13 14:27:56")); 273 | 274 | obj.ToDynamic()._FieldB = DateTime.Parse("2016/12/10 03:07:04"); 275 | 276 | var actual2 = (DateTime)obj.ToDynamic()._FieldB; 277 | actual2.Is(DateTime.Parse("2016/12/10 03:07:04")); 278 | } 279 | 280 | [Fact] 281 | public void CallOverloadedPrivateStaticMethod_of_DerivedClass_by_Dynamic() 282 | { 283 | var binder = DynamicBinder.Create(); 284 | 285 | var actual1 = (string)binder.MethodF("Emperor", 46); 286 | actual1.Is("Method-F(int): Emperor / 46"); 287 | 288 | var actual2 = (string)binder.MethodF("Strap", 19.28); 289 | actual2.Is("Method-F(double): Strap / 19.28"); 290 | } 291 | 292 | [Fact] 293 | public void GetAndSetPrivateStaticProperty_of_DerivedClass_by_Dynamic() 294 | { 295 | var binder = DynamicBinder.Create(typeof(DerivedTestTargetClass)); 296 | var actual1 = (string)binder.PropD; 297 | actual1.IsNull(); 298 | 299 | try 300 | { 301 | binder.PropD = "Dynamic FizzBuzz"; 302 | 303 | var actual2 = (string)binder.PropD; 304 | actual2.Is("Dynamic FizzBuzz"); 305 | } 306 | finally 307 | { 308 | binder.PropD = null; 309 | } 310 | } 311 | 312 | [Fact] 313 | public void GetAndSetPrivateStaticField_of_DerivedClass_by_Dynamic() 314 | { 315 | var obj = new DerivedTestTargetClass(); 316 | var binder = DynamicBinder.Create(obj.GetType()); 317 | var actual1 = (string)binder._FieldE; 318 | actual1.Is("Static Foo"); 319 | 320 | try 321 | { 322 | binder._FieldE = "Static Dynamic Bar"; 323 | 324 | var actual2 = (string)binder._FieldE; 325 | actual2.Is("Static Dynamic Bar"); 326 | } 327 | finally 328 | { 329 | binder._FieldE = "Static Foo"; 330 | } 331 | } 332 | 333 | [Fact] 334 | public void CallPrivateInstanceMethod_with_ref_and_out_Argument_by_Dynamic() 335 | { 336 | object obj = new TestTargetClass(); 337 | 338 | var y = 6; 339 | obj.ToDynamic().MethodG(5, ref y, out int z); 340 | 341 | y.Is(7); 342 | z.Is(30); 343 | } 344 | 345 | [Fact] 346 | public void CallPrivateStaticMethod_with_ref_and_out_Argument_by_Dynamic() 347 | { 348 | var binder = DynamicBinder.Create(); 349 | 350 | var y = 6; 351 | binder.MethodH(5, ref y, out int z); 352 | 353 | y.Is(16); 354 | z.Is(10); 355 | } 356 | 357 | [Fact] 358 | public void RetrieveObject_and_UseIt_by_Dynamic() 359 | { 360 | // Given 361 | var binder = DynamicBinder.Create(); 362 | var subItem = binder.GetSubItem(); 363 | 364 | // When 365 | string name = binder.GetNameOfSubItem(subItem); 366 | 367 | // Then 368 | name.Is("John"); 369 | ((int)subItem.Value).Is(40); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /DynamicBinder.Test/LateBinderTest.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Toolbelt; 3 | using Toolbelt.DynamicBinderExtension; 4 | using Xunit; 5 | 6 | namespace DynamicBinderTest; 7 | 8 | public class LateBinderTest 9 | { 10 | [Fact] 11 | public void CreateInstanceT_for_public_constructor() 12 | { 13 | var obj = LateBinder.CreateInstance(); 14 | obj.Prop["PropA"].Is("Fizz"); 15 | ((DateTime)obj.Field["_FieldB"]).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 16 | } 17 | 18 | [Fact] 19 | public void CreateInstanceT_for_private_constructor_1() 20 | { 21 | var obj = LateBinder.CreateInstance("Lorem"); 22 | obj.Prop["PropA"].Is("Lorem"); 23 | ((DateTime)obj.Field["_FieldB"]).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 24 | } 25 | 26 | [Fact] 27 | public void CreateInstanceT_for_private_constructor_2() 28 | { 29 | var obj = LateBinder.CreateInstance("Ipsum", DateTime.Parse("2022/03/27 15:49:10")); 30 | obj.Prop["PropA"].Is("Ipsum"); 31 | ((DateTime)obj.Field["_FieldB"]).ToString("yyyy/MM/dd HH:mm:ss").Is("2022/03/27 15:49:10"); 32 | } 33 | 34 | [Fact] 35 | public void CreateInstance_for_public_constructor() 36 | { 37 | var obj = LateBinder.CreateInstance(typeof(TestTargetClass)); 38 | obj.Prop["PropA"].Is("Fizz"); 39 | ((DateTime)obj.Field["_FieldB"]).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 40 | } 41 | 42 | [Fact] 43 | public void CreateInstance_for_private_constructor_1() 44 | { 45 | var obj = LateBinder.CreateInstance(typeof(TestTargetClass), "Lorem"); 46 | obj.Prop["PropA"].Is("Lorem"); 47 | ((DateTime)obj.Field["_FieldB"]).ToString("yyyy/MM/dd HH:mm:ss").Is("2014/02/13 14:27:56"); 48 | } 49 | 50 | [Fact] 51 | public void CreateInstance_for_private_constructor_2() 52 | { 53 | var obj = LateBinder.CreateInstance(typeof(TestTargetClass), "Ipsum", DateTime.Parse("2022/03/27 15:49:10")); 54 | obj.Prop["PropA"].Is("Ipsum"); 55 | ((DateTime)obj.Field["_FieldB"]).ToString("yyyy/MM/dd HH:mm:ss").Is("2022/03/27 15:49:10"); 56 | } 57 | 58 | [Fact] 59 | public void CreateInstance_with_DynamicObject_by_Dynamic() 60 | { 61 | // Given 62 | var testTarget = LateBinder.CreateInstance(); 63 | var subItemA = testTarget.Call("CreateSubItem1").ToLateBind(); 64 | 65 | // When 66 | var subItemB = LateBinder.CreateInstance(subItemA); 67 | 68 | // Then 69 | subItemB.Prop["Name"].Is("Jude"); 70 | subItemB.Prop["Value"].Is(47); 71 | } 72 | 73 | [Fact] 74 | public void CallOverloadedPrivateInstanceMethod_by_LateBinder() 75 | { 76 | object obj = new TestTargetClass(); 77 | 78 | obj.ToLateBind().Call("MethodC", "Adelie") 79 | .IsInstanceOf() 80 | .Is("Method-C by string: Adelie"); 81 | 82 | obj.ToLateBind().Call("MethodC", 27) 83 | .IsInstanceOf() 84 | .Is(27); 85 | } 86 | 87 | [Fact] 88 | public void GetAndSetPrivateInstanceProperty_by_LateBinder() 89 | { 90 | object obj = new TestTargetClass(); 91 | obj.ToLateBind().Prop["PropA"] 92 | .IsInstanceOf() 93 | .Is("Fizz"); 94 | 95 | obj.ToLateBind().Prop["PropA"] = "Buzz"; 96 | 97 | obj.ToLateBind().Prop["PropA"] 98 | .Is("Buzz"); 99 | } 100 | 101 | [Fact] 102 | public void GetAndSetPrivateInstanceField_by_LateBinder() 103 | { 104 | object obj = new TestTargetClass(); 105 | obj.ToLateBind().Field["_FieldB"] 106 | .IsInstanceOf() 107 | .Is(DateTime.Parse("2014/02/13 14:27:56")); 108 | 109 | obj.ToLateBind().Field["_FieldB"] = DateTime.Parse("2015/11/09 02:06:03"); 110 | 111 | obj.ToLateBind().Field["_FieldB"] 112 | .Is(DateTime.Parse("2015/11/09 02:06:03")); 113 | } 114 | 115 | [Fact] 116 | public void CallOverloadedPrivateStaticMethod_by_LateBinder() 117 | { 118 | var binder = LateBinder.Create(typeof(TestTargetClass)); 119 | 120 | binder.Call("MethodF", "Gentoo", 18) 121 | .IsInstanceOf() 122 | .Is("Method-F(int): Gentoo / 18"); 123 | 124 | binder.Call("MethodF", "RockHoper", 31.4) 125 | .IsInstanceOf() 126 | .Is("Method-F(double): RockHoper / 31.4"); 127 | } 128 | 129 | [Fact] 130 | public void GetAndSetPrivateStaticProperty_by_LateBinder() 131 | { 132 | var binder = LateBinder.Create(); 133 | binder.Prop["PropD"] 134 | .IsNull(); 135 | 136 | try 137 | { 138 | binder.Prop["PropD"] = "FizzBuzz"; 139 | 140 | binder.Prop["PropD"] 141 | .IsInstanceOf() 142 | .Is("FizzBuzz"); 143 | } 144 | finally 145 | { 146 | binder.Prop["PropD"] = null; 147 | } 148 | } 149 | 150 | [Fact] 151 | public void GetAndSetPrivateStaticField_by_LateBinder() 152 | { 153 | object obj = new TestTargetClass(); 154 | var binder = LateBinder.Create(obj.GetType()); 155 | 156 | binder.Field["_FieldE"] 157 | .IsInstanceOf() 158 | .Is("Static Foo"); 159 | 160 | try 161 | { 162 | binder.Field["_FieldE"] = "Static Bar"; 163 | 164 | binder.Field["_FieldE"] 165 | .Is("Static Bar"); 166 | } 167 | finally 168 | { 169 | binder.Field["_FieldE"] = "Static Foo"; 170 | } 171 | } 172 | 173 | // ------------------------- 174 | 175 | [Fact] 176 | public void CallOverloadedPrivateInstanceMethod_of_DerivedClass_by_LateBinder() 177 | { 178 | object obj = new DerivedTestTargetClass(); 179 | 180 | obj.ToLateBind().Call("MethodC", "Adelie") 181 | .IsInstanceOf() 182 | .Is("Method-C by string: Adelie"); 183 | 184 | obj.ToLateBind().Call("MethodC", 27) 185 | .IsInstanceOf() 186 | .Is(27); 187 | } 188 | 189 | [Fact] 190 | public void GetAndSetPrivateInstanceProperty_of_DerivedClass_by_LateBinder() 191 | { 192 | object obj = new DerivedTestTargetClass(); 193 | obj.ToLateBind().Prop["PropA"] 194 | .IsInstanceOf() 195 | .Is("Fizz"); 196 | 197 | obj.ToLateBind().Prop["PropA"] = "Buzz"; 198 | 199 | obj.ToLateBind().Prop["PropA"] 200 | .Is("Buzz"); 201 | } 202 | 203 | [Fact] 204 | public void GetAndSetPrivateInstanceField_of_DerivedClass_by_LateBinder() 205 | { 206 | object obj = new DerivedTestTargetClass(); 207 | obj.ToLateBind().Field["_FieldB"] 208 | .IsInstanceOf() 209 | .Is(DateTime.Parse("2014/02/13 14:27:56")); 210 | 211 | obj.ToLateBind().Field["_FieldB"] = DateTime.Parse("2015/11/09 02:06:03"); 212 | 213 | obj.ToLateBind().Field["_FieldB"] 214 | .Is(DateTime.Parse("2015/11/09 02:06:03")); 215 | } 216 | 217 | [Fact] 218 | public void CallOverloadedPrivateStaticMethod_of_DerivedClass_by_LateBinder() 219 | { 220 | var binder = LateBinder.Create(typeof(DerivedTestTargetClass)); 221 | 222 | binder.Call("MethodF", "Gentoo", 18) 223 | .IsInstanceOf() 224 | .Is("Method-F(int): Gentoo / 18"); 225 | 226 | binder.Call("MethodF", "RockHoper", 31.4) 227 | .IsInstanceOf() 228 | .Is("Method-F(double): RockHoper / 31.4"); 229 | } 230 | 231 | [Fact] 232 | public void GetAndSetPrivateStaticProperty_of_DerivedClass_by_LateBinder() 233 | { 234 | var binder = LateBinder.Create(); 235 | binder.Prop["PropD"] 236 | .IsNull(); 237 | 238 | try 239 | { 240 | binder.Prop["PropD"] = "FizzBuzz"; 241 | 242 | binder.Prop["PropD"] 243 | .IsInstanceOf() 244 | .Is("FizzBuzz"); 245 | } 246 | finally 247 | { 248 | binder.Prop["PropD"] = null; 249 | } 250 | } 251 | 252 | [Fact] 253 | public void GetAndSetPrivateStaticField_of_DerivedClass_by_LateBinder() 254 | { 255 | object obj = new DerivedTestTargetClass(); 256 | var binder = LateBinder.Create(obj.GetType()); 257 | 258 | binder.Field["_FieldE"] 259 | .IsInstanceOf() 260 | .Is("Static Foo"); 261 | 262 | try 263 | { 264 | binder.Field["_FieldE"] = "Static Bar"; 265 | 266 | binder.Field["_FieldE"] 267 | .Is("Static Bar"); 268 | } 269 | finally 270 | { 271 | binder.Field["_FieldE"] = "Static Foo"; 272 | } 273 | } 274 | 275 | [Fact] 276 | public void CallOverloadedPrivateInstanceMethod_by_LateBinder_with_Cache() 277 | { 278 | object obj = new TestTargetClass(); 279 | var cache = new Dictionary>(); 280 | 281 | obj.ToLateBind(cache).Call("MethodC", "Adelie") 282 | .IsInstanceOf() 283 | .Is("Method-C by string: Adelie"); 284 | 285 | obj.ToLateBind(cache).Call("MethodC", 27) 286 | .IsInstanceOf() 287 | .Is(27); 288 | 289 | obj.ToLateBind(cache).Call("MethodC", "Emperor") 290 | .IsInstanceOf() 291 | .Is("Method-C by string: Emperor"); 292 | } 293 | 294 | [Fact] 295 | public void CallPrivateInstanceMethod_with_ref_and_out_Argument_by_LateBinder() 296 | { 297 | object obj = new TestTargetClass(); 298 | 299 | var args = new object[] { 3, 4, default(int) }; 300 | obj.ToLateBind().Call("MethodG", args); 301 | 302 | args[1].Is(5); 303 | args[2].Is(12); 304 | } 305 | 306 | [Fact] 307 | public void CallPrivateStaticMethod_with_ref_and_out_Argument_by_LateBinder() 308 | { 309 | var binder = LateBinder.Create(); 310 | 311 | var args = new object[] { 3, 4, default(int) }; 312 | binder.Call("MethodH", args); 313 | 314 | args[1].Is(10); 315 | args[2].Is(6); 316 | } 317 | 318 | [Fact] 319 | public void RetrieveObject_and_UseIt_by_LateBinder() 320 | { 321 | // Given 322 | var binder = LateBinder.Create(); 323 | var subItem = binder.Call("GetSubItem").ToLateBind(); 324 | 325 | // When 326 | var name = binder.Call("GetNameOfSubItem", subItem) as string; 327 | 328 | // Then 329 | name.Is("John"); 330 | ((int)subItem.Prop["Value"]).Is(40); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /DynamicBinder.Test/TestTargetClass.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 414 // disable warning CS0414: The field '*' is assigned but its value is never used. 2 | 3 | namespace DynamicBinderTest; 4 | 5 | public class TestTargetClass 6 | { 7 | public class SubItemClass1 8 | { 9 | public string Name { get; set; } 10 | private int Value { get; set; } 11 | public SubItemClass1(string name, int value) { this.Name = name; this.Value = value; } 12 | public SubItemClass1(SubItemClass1 subItem) { this.Name = subItem.Name; this.Value = subItem.Value; } 13 | public override string ToString() { return "Good Job!"; } 14 | } 15 | 16 | private class SubItemClass2 17 | { 18 | public string Name { get; set; } 19 | private int Value { get; set; } 20 | public SubItemClass2(string name, int value) { this.Name = name; this.Value = value; } 21 | } 22 | 23 | // Instance members 24 | // ============== 25 | 26 | private string PropA { get; set; } 27 | 28 | private DateTime _FieldB = DateTime.Parse("2014/02/13 14:27:56"); 29 | 30 | private SubItemClass1 PropG { get; set; } 31 | 32 | private SubItemClass2 PropH { get; set; } 33 | 34 | /// 35 | /// Public Constractor 36 | /// 37 | public TestTargetClass() 38 | { 39 | this.PropA = "Fizz"; 40 | this.PropG = new SubItemClass1("Sam", 33); 41 | this.PropH = new SubItemClass2("Alice", 29); 42 | } 43 | 44 | /// 45 | /// Private Constractor 1 46 | /// 47 | private TestTargetClass(string propA) : this() 48 | { 49 | this.PropA = propA; 50 | } 51 | 52 | /// 53 | /// Private Constractor 2 54 | /// 55 | private TestTargetClass(string propA, DateTime fieldB) : this(propA) 56 | { 57 | this._FieldB = fieldB; 58 | } 59 | 60 | private string MethodC(string name) 61 | { 62 | return "Method-C by string: " + name; 63 | } 64 | 65 | private int MethodC(int age) 66 | { 67 | return age; 68 | } 69 | 70 | private void MethodG(int x, ref int y, out int z) 71 | { 72 | z = x * y; 73 | y = y + 1; 74 | } 75 | 76 | public SubItemClass1 CreateSubItem1() 77 | { 78 | return new SubItemClass1("Jude", 47); 79 | } 80 | 81 | // Static members 82 | // ============== 83 | 84 | private static string PropD { get; set; } 85 | 86 | private static string _FieldE = "Static Foo"; 87 | 88 | private static string MethodF(string name, int age) 89 | { 90 | return "Method-F(int): " + name + " / " + age.ToString(); 91 | } 92 | 93 | private static string MethodF(string name, double age) 94 | { 95 | return "Method-F(double): " + name + " / " + age.ToString(); 96 | } 97 | 98 | private static void MethodH(int x, ref int y, out int z) 99 | { 100 | z = x * 2; 101 | y = y + z; 102 | } 103 | 104 | private static SubItemClass2 GetSubItem() 105 | { 106 | return new SubItemClass2("John", 40); 107 | } 108 | 109 | private static string GetNameOfSubItem(SubItemClass2 subItem) 110 | { 111 | return subItem.Name; 112 | } 113 | } 114 | 115 | public class DerivedTestTargetClass : TestTargetClass 116 | { 117 | } 118 | -------------------------------------------------------------------------------- /DynamicBinder.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32317.152 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicBinder", "DynamicBinder\DynamicBinder.csproj", "{005BD7D1-5601-421F-8AB1-9EBBC2B0128D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "README", "README", "{96C3190E-5747-41F6-A888-C7C7293FB083}" 9 | ProjectSection(SolutionItems) = preProject 10 | LICENSE = LICENSE 11 | README.md = README.md 12 | RELEASE-NOTES.txt = RELEASE-NOTES.txt 13 | EndProjectSection 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicBinder.Test", "DynamicBinder.Test\DynamicBinder.Test.csproj", "{DA985DF9-0C2F-472F-BD3C-44BDD095436A}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Actions", "GitHub Actions", "{F770FA13-066F-4457-A073-3385609B88EF}" 18 | ProjectSection(SolutionItems) = preProject 19 | .github\workflows\unit-tests.yml = .github\workflows\unit-tests.yml 20 | EndProjectSection 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {005BD7D1-5601-421F-8AB1-9EBBC2B0128D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {005BD7D1-5601-421F-8AB1-9EBBC2B0128D}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {005BD7D1-5601-421F-8AB1-9EBBC2B0128D}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {005BD7D1-5601-421F-8AB1-9EBBC2B0128D}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {DA985DF9-0C2F-472F-BD3C-44BDD095436A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {DA985DF9-0C2F-472F-BD3C-44BDD095436A}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {DA985DF9-0C2F-472F-BD3C-44BDD095436A}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {DA985DF9-0C2F-472F-BD3C-44BDD095436A}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {10F0692E-A4EE-4D1E-B2A1-CDE0B1CD0DBD} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /DynamicBinder/Binder.cs: -------------------------------------------------------------------------------- 1 | namespace Toolbelt 2 | { 3 | internal static class Binder 4 | { 5 | internal static void UnwrapBinder(object[] args) 6 | { 7 | for (var i = 0; i < args.Length; i++) 8 | { 9 | if (args[i] is DynamicBinder dynamicBinder) args[i] = dynamicBinder.Object; 10 | else if (args[i] is LateBinder lateBinder) args[i] = lateBinder.Object; 11 | } 12 | } 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DynamicBinder/DynamicBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Dynamic; 3 | using System.Globalization; 4 | using System.Reflection; 5 | using Toolbelt.DynamicBinderExtension; 6 | 7 | namespace Toolbelt 8 | { 9 | public class DynamicBinder : DynamicObject 10 | { 11 | protected LateBinder _Binder; 12 | 13 | internal DynamicBinder(LateBinder accessor) 14 | { 15 | this._Binder = accessor; 16 | } 17 | 18 | public static dynamic CreateInstance(params object[] args) 19 | { 20 | return CreateInstance(typeof(T), args); 21 | } 22 | 23 | public static dynamic CreateInstance(Type type, params object[] args) 24 | { 25 | Binder.UnwrapBinder(args); 26 | 27 | var obj = Activator.CreateInstance(type, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, args, default(CultureInfo)); 28 | return obj.ToDynamic(); 29 | } 30 | 31 | public DynamicBinder(object target) 32 | { 33 | this._Binder = new LateBinder(target); 34 | } 35 | 36 | /// get the object that dynamic binding taret. 37 | public object Object { get { return this._Binder.Object; } } 38 | 39 | 40 | public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) 41 | { 42 | Binder.UnwrapBinder(args); 43 | 44 | if (base.TryInvokeMember(binder, args, out result)) return true; 45 | result = Wrap(this._Binder.Call(binder.Name, args)); 46 | return true; 47 | } 48 | 49 | public override bool TryGetMember(GetMemberBinder binder, out object result) 50 | { 51 | if (base.TryGetMember(binder, out result)) return true; 52 | if (this._Binder.Prop.Has(binder.Name)) 53 | { 54 | result = Wrap(this._Binder.Prop[binder.Name]); 55 | return true; 56 | } 57 | else if (this._Binder.Field.Has(binder.Name)) 58 | { 59 | result = Wrap(this._Binder.Field[binder.Name]); 60 | return true; 61 | } 62 | return false; 63 | } 64 | 65 | public override bool TrySetMember(SetMemberBinder binder, object value) 66 | { 67 | if (base.TrySetMember(binder, value)) return true; 68 | if (this._Binder.Prop.Has(binder.Name)) 69 | { 70 | this._Binder.Prop[binder.Name] = value; 71 | return true; 72 | } 73 | else if (this._Binder.Field.Has(binder.Name)) 74 | { 75 | this._Binder.Field[binder.Name] = value; 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | public override bool TryConvert(ConvertBinder binder, out object result) 82 | { 83 | if (base.TryConvert(binder, out result)) return true; 84 | result = this._Binder.Object; 85 | return true; 86 | } 87 | 88 | private static object Wrap(object obj) 89 | { 90 | return obj == null ? null : 91 | Type.GetTypeCode(obj.GetType()) == TypeCode.Object ? 92 | new DynamicBinder(obj) : obj; 93 | } 94 | 95 | public static dynamic Create() 96 | { 97 | return new DynamicBinder(LateBinder.Create()); 98 | } 99 | 100 | public static dynamic Create(Type type) 101 | { 102 | return new DynamicBinder(LateBinder.Create(type)); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /DynamicBinder/DynamicBinder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 2.2.0 7 | DynamicBinder and LateBinder 8 | J.Sakamoto 9 | J.Sakamoto 10 | Copyright © J.Sakamoto 2014-2023 11 | This library allows you dynamic access to object methods, properties, and fields by using the reflection technology of .NET, regardless of whether they are private members. You can access both object instance members and class static members by name that specified string argument at runtime, not compile-time, or C# 4.0 "dynamic" syntax. 12 | 13 | (Please write the package release notes in "../RELEASE-NOTES.txt") 14 | LGPL-3.0-or-later 15 | https://github.com/jsakamoto/dynamicbinder 16 | reflection,dynamic,late binding 17 | README.md 18 | nupkg-icon.png 19 | ../_dist 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | @(ReleaseNoteLines, '%0a') 33 | $([System.Text.RegularExpressions.Regex]::Match($(PackageReleaseNotes), "^(v\.[\d\.]+.+?)v\.[\d\.]+", System.Text.RegularExpressions.RegexOptions.Singleline).Groups[1].Value) 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /DynamicBinder/DynamicBinderExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Toolbelt.DynamicBinderExtension 6 | { 7 | public static class DynamicBinderExtension 8 | { 9 | public static LateBinder ToLateBind(this object self) 10 | { 11 | return new LateBinder(self); 12 | } 13 | 14 | public static LateBinder ToLateBind(this object self, IDictionary> cache) 15 | { 16 | return new LateBinder(self).SetCache(cache); 17 | } 18 | 19 | public static dynamic ToDynamic(this object self) 20 | { 21 | return new DynamicBinder(new LateBinder(self)); 22 | } 23 | 24 | public static dynamic ToDynamic(this object self, IDictionary> cache) 25 | { 26 | return new DynamicBinder(new LateBinder(self).SetCache(cache)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DynamicBinder/LateBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Toolbelt.DynamicBinderExtension; 7 | 8 | namespace Toolbelt 9 | { 10 | public class LateBinder 11 | { 12 | protected object _Target; 13 | 14 | protected Type _TypeOfTarget; 15 | 16 | protected BindingFlags _BindingFlags; 17 | 18 | public static LateBinder CreateInstance(params object[] args) 19 | { 20 | return CreateInstance(typeof(T), args); 21 | } 22 | 23 | public static LateBinder CreateInstance(Type type, params object[] args) 24 | { 25 | Binder.UnwrapBinder(args); 26 | var obj = Activator.CreateInstance(type, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, args, default(CultureInfo)); 27 | return obj.ToLateBind(); 28 | } 29 | 30 | protected IDictionary> _Cache; 31 | 32 | public PropertyBinder Prop { get; protected set; } 33 | 34 | public FieldBinder Field { get; protected set; } 35 | 36 | public LateBinder(object target) 37 | { 38 | this.Initi(target, target.GetType()); 39 | } 40 | 41 | protected LateBinder(object target, Type typeOfTarget) 42 | { 43 | this.Initi(target, typeOfTarget); 44 | } 45 | 46 | private void Initi(object target, Type typeOfTarget) 47 | { 48 | this._Target = target; 49 | this._TypeOfTarget = typeOfTarget; 50 | this._BindingFlags = BindingFlags.Public | BindingFlags.NonPublic | (target != null ? BindingFlags.Instance : BindingFlags.Static); 51 | this.Prop = new PropertyBinder(this); 52 | this.Field = new FieldBinder(this); 53 | } 54 | 55 | public static LateBinder Create(Type type) 56 | { 57 | return new LateBinder(null, type); 58 | } 59 | 60 | public static LateBinder Create() 61 | { 62 | return new LateBinder(null, typeof(T)); 63 | } 64 | 65 | public LateBinder SetCache(IDictionary> cache) 66 | { 67 | this._Cache = cache; 68 | return this; 69 | } 70 | 71 | /// get the object that late binding taret. 72 | public object Object { get { return this._Target; } } 73 | 74 | public object Call(string methodName, params object[] args) 75 | { 76 | Binder.UnwrapBinder(args); 77 | var argTypes = args.Select(_ => _ != null ? _.GetType() : typeof(object)).ToArray(); 78 | var memberSufix = "(" + string.Join(",", argTypes.Select(t => t.FullName)) + ")"; 79 | var methodInfo = this.FindMember( 80 | methodName, 81 | finder: t => 82 | { 83 | var method = t.GetMethod(methodName, this._BindingFlags, null, argTypes, null); 84 | if (method != null) return method; 85 | var methods = t.GetMethods(this._BindingFlags).Where(m => m.Name == methodName).ToArray(); 86 | if (methods.Length == 1) return methods.First(); 87 | return methods.Where(m => 88 | { 89 | var parameters = m.GetParameters(); 90 | return parameters 91 | .Select((p, index) => p.ParameterType.FullName.TrimEnd('&') == argTypes[index].FullName) 92 | .All(_ => _); 93 | }).FirstOrDefault(); 94 | }, 95 | memberSufix: memberSufix); 96 | return methodInfo.Invoke(this._Target, args); 97 | } 98 | 99 | protected static IEnumerable EnumType(Type type) 100 | { 101 | for (; type != null; type = type.BaseType) 102 | { 103 | yield return type; 104 | } 105 | } 106 | 107 | protected IDictionary GetCacheOfMe() 108 | { 109 | if (this._Cache == null) return null; 110 | var cacheOfMe = default(IDictionary); 111 | lock (this._Cache) 112 | { 113 | if (this._Cache.TryGetValue(this._TypeOfTarget, out cacheOfMe) == false) 114 | { 115 | cacheOfMe = new Dictionary(); 116 | this._Cache.Add(this._TypeOfTarget, cacheOfMe); 117 | } 118 | } 119 | return cacheOfMe; 120 | } 121 | 122 | internal T FindMember( 123 | string memberName, 124 | Func finder, 125 | bool throwExceptionIfMemberNotFound = true, 126 | string memberSufix = "" 127 | ) where T : MemberInfo 128 | { 129 | Func findMember = () => 130 | { 131 | var memberInfo = EnumType(this._TypeOfTarget) 132 | .Select(t => finder(t)) 133 | .FirstOrDefault(m => m != null); 134 | if (memberInfo == null && throwExceptionIfMemberNotFound) 135 | throw new Exception("Member " + memberName + memberSufix + " not found."); 136 | return memberInfo; 137 | }; 138 | 139 | var cacheOfMe = this.GetCacheOfMe(); 140 | if (cacheOfMe == null) 141 | { 142 | return findMember(); 143 | } 144 | else 145 | { 146 | lock (cacheOfMe) 147 | { 148 | var memberInfo = default(MemberInfo); 149 | if (cacheOfMe.TryGetValue(memberName + memberSufix, out memberInfo) == false) 150 | { 151 | memberInfo = findMember(); 152 | cacheOfMe.Add(memberName + memberSufix, memberInfo); 153 | } 154 | return (T)memberInfo; 155 | } 156 | } 157 | } 158 | 159 | public class PropertyBinder 160 | { 161 | protected LateBinder _Binder; 162 | 163 | internal PropertyBinder(LateBinder accessor) 164 | { 165 | this._Binder = accessor; 166 | } 167 | 168 | public PropertyInfo GetInfo(string propName, bool throwExceptionIfMemberNotFound = true) 169 | { 170 | return this._Binder.FindMember(propName, t => t.GetProperty(propName, this._Binder._BindingFlags), throwExceptionIfMemberNotFound); 171 | } 172 | 173 | public object this[string propName] 174 | { 175 | get 176 | { 177 | var propInfo = this.GetInfo(propName); 178 | return propInfo.GetValue(this._Binder._Target, null); 179 | } 180 | set 181 | { 182 | var propInfo = this.GetInfo(propName); 183 | propInfo.SetValue(this._Binder._Target, value, null); 184 | } 185 | } 186 | 187 | public bool Has(string propName) 188 | { 189 | return this.GetInfo(propName, false) != null; 190 | } 191 | } 192 | 193 | public class FieldBinder 194 | { 195 | protected LateBinder _Binder; 196 | 197 | internal FieldBinder(LateBinder accessor) 198 | { 199 | this._Binder = accessor; 200 | } 201 | 202 | public FieldInfo GetInfo(string fieldName, bool throwExceptionIfMemberNotFound = true) 203 | { 204 | return this._Binder.FindMember(fieldName, t => t.GetField(fieldName, this._Binder._BindingFlags), throwExceptionIfMemberNotFound); 205 | } 206 | 207 | public object this[string fieldName] 208 | { 209 | get 210 | { 211 | var fieldInfo = this.GetInfo(fieldName); 212 | return fieldInfo.GetValue(this._Binder._Target); 213 | } 214 | set 215 | { 216 | var fieldInfo = this.GetInfo(fieldName); 217 | fieldInfo.SetValue(this._Binder._Target, value); 218 | } 219 | } 220 | 221 | public bool Has(string fieldName) 222 | { 223 | return this.GetInfo(fieldName, false) != null; 224 | } 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DynamicBinder and LateBinder [![NuGet Package](https://img.shields.io/nuget/v/DynamicBinder.svg)](https://www.nuget.org/packages/DynamicBinder/) [![unit tests](https://github.com/jsakamoto/dynamicbinder/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/jsakamoto/dynamicbinder/actions/workflows/unit-tests.yml) 2 | 3 | ## What's this? 4 | 5 | This is the class library for .NET. 6 | 7 | This library allows you dynamic access to object methods, properties, and fields using the reflection technology of .NET, regardless of whether they are private members. 8 | 9 | You can access both objects' instance members and classes' static members by name that specified string argument at runtime, not compile-time, or C# 4.0 "dynamic" syntax. 10 | 11 | ## How to install? 12 | 13 | You can install this library via [NuGet](https://www.nuget.org/packages/DynamicBinder/). 14 | 15 | ```shell 16 | > dotnet add package DynamicBinder 17 | ``` 18 | 19 | ## Usage - C# "dynamic" syntax 20 | 21 | ### Create instance 22 | 23 | After importing (opening) namespace `Toolbelt`, you can use `DynamicBinder.CreateInstance(...)` and `DynamicBinder.CreateInstance(Type t, ...)` static method to instantiate any objects with any constructors, regardless of constructor's access level (public, internal, protected, private). 24 | 25 | Those methods return an instantiated object wrapped with the `DynamicBinder` object as a `dynamic` type. 26 | 27 | ```csharp 28 | using Toolbelt; 29 | ... 30 | // 👇 The type of the "dynamicObj" is the "dynamic" type. 31 | // In this case, the "dynamicObj" is instantiated by the constructor, which has two arguments. 32 | // It can be instantiated even if the constructor is private. 33 | var dynamicObj = DynamicBinder.CreateInstance(arg1, arg2); 34 | 35 | // And it can be invoked its instance methods, regardless of its access level. 36 | var retval = (int)dynamicObj.PrivateMethodName(arg3, arg4); 37 | ... 38 | ``` 39 | 40 | ### Access to instance members 41 | 42 | After importing (opening) namespace `Toolbelt.DynamicBinderExtension`, you can use `ToDynamic()` extension method that returned C #4.0 `dynamic` type at any object. 43 | 44 | ```csharp 45 | using Toolbelt.DynamicBinderExtension; 46 | ... 47 | var obj = new MyClass(); 48 | ... 49 | // Call an instance method. 50 | // (You can pass ref & out arguments.) 51 | var retval = (int)obj.ToDynamic().MethodName(arg1, ref arg2, out int arg3); 52 | 53 | // Get or set an instance property. 54 | var value = (int)obj.ToDynamic().PropName; 55 | obj.ToDynamic().PropName = newValue; 56 | 57 | // Get or set an instance field. 58 | var value = (int)obj.ToDynamic().FieldName; 59 | obj.ToDynamic().FieldName = newValue; 60 | ``` 61 | 62 | ### Access to static members 63 | 64 | After importing (opening) namespace `Toolbelt`, you can use `DynamicBinder.Create()` and `DynamicBinder.Create(Type t)` static method that returned C #4.0 `dynamic` type. 65 | 66 | ```csharp 67 | using Toolbelt; 68 | ... 69 | var binder = DynamicBinder.Create(typeof(Foo)); 70 | 71 | // Call a static method. 72 | // (You can pass ref & out arguments.) 73 | var retval = (int)binder.MethodName(arg1, ref arg2, out int arg3); 74 | 75 | // Get or set a static property. 76 | var value = (int)binder.PropName; 77 | binder.PropName = newValue; 78 | 79 | // Get or set a static field. 80 | var value = (int)binder.FieldName; 81 | binder.FieldName = newValue; 82 | ``` 83 | 84 | ### NOTICE: Retrieving a type information of the returned value from the method calling 85 | 86 | Retrieving a type information of the return value from the method calling 87 | 88 | The following test code will fail. 89 | 90 | ```csharp 91 | object bar = foo.ToDynamic().GetBarObject(); 92 | 93 | // 👇 It will report "actual is `DynamicBinder`" ! 94 | Assert.AreEqual("BarClass", bar.GetType().Name); 95 | ``` 96 | 97 | You should rewrite the above test code as follows. 98 | 99 | ```csharp 100 | // Extract a `DynamicBinder` object from the C# dynamic object by casting with `as`. 101 | var retval = foo.ToDynamic().GetBarObject() as DynamicBinder; 102 | 103 | // The `DynamicBinder` class exposes the `Object` property to access the bound target object. 104 | Assert.AreEqual("BarClass", retval.Object.GetType().Name); // Green. Pass! 105 | ``` 106 | 107 | Of course, if you have the right type information, those test codes can be rewritten as the following: 108 | 109 | ```csharp 110 | var bar = (BarClass)foo.ToDynamic().GetBarObject(); 111 | Assert.AreEqual("BarClass", bar.GetType().Name); // Green. Pass! 112 | ``` 113 | 114 | 115 | ## Usage - Late bind syntax 116 | 117 | ### Create instance 118 | 119 | After importing (opening) namespace `Toolbelt`, you can use `LateBinder.CreateInstance(...)` and `LateBinder.CreateInstance(Type t, ...)` static method to instantiate any objects with any constructors, regardless of constructor's access level (public, internal, protected, private). 120 | 121 | Those methods return an instantiated object wrapped with the `LateBinder` type. 122 | 123 | ```csharp 124 | using Toolbelt; 125 | ... 126 | // 👇 The type of the "dynamicObj" is the "LateBinder" type. 127 | // In this case, the "dynamicObj" is instantiated by the constructor, which has two arguments. 128 | // It can be instantiated even if the constructor is private. 129 | var dynamicObj = LateBinder.CreateInstance(arg1, arg2); 130 | 131 | // And it can be invoked its instance methods, regardless of its access level. 132 | var retval = (int)dynamicObj.Call("PrivateMethodName", arg3, arg4); 133 | ... 134 | ``` 135 | 136 | ### Access to instance members 137 | 138 | After importing (opening) namespace `Toolbelt.DynamicBinderExtension`, you can use `ToLateBind()` extension method that returned `LateBinder` object at any object. 139 | 140 | `LateBinder` has follow members. 141 | 142 | - `Call(name, params[] args)` method 143 | - `Prop[name]` property 144 | - `Field[name]` property 145 | 146 | ```csharp 147 | using Toolbelt.DynamicBinderExtension; 148 | ... 149 | // Call an instance method. 150 | var retval = (int)obj.ToLateBind().Call("MethodName", arg1, arg2); 151 | 152 | // Get or set an instance property. 153 | var value = (int)obj.ToLateBind().Prop["PropName"]; 154 | obj.ToLateBind().Prop["PropName"] = newValue; 155 | 156 | // Get or set an instance field. 157 | var value = (int)obj.ToLateBind().Field["FieldName"]; 158 | obj.ToLateBind().Field["FieldName"] = newValue; 159 | ``` 160 | 161 | ### Access to static members 162 | 163 | After importing (opening) namespace `Toolbelt`, you can use `LateBinder.Create()` and `LateBinder.Create(Type t)` static method that returned `LateBinder` object. 164 | 165 | ```csharp 166 | using Toolbelt; 167 | ... 168 | var binder = LateBinder.Create(); 169 | // Call a static method. 170 | var retval = (int)binder.Call("MethodName", arg1, arg2); 171 | 172 | // Get or set a static property. 173 | var value = (int)binder.Prop["PropName"]; 174 | binder.Prop["PropName"] = newValue; 175 | 176 | // Get or set a static field. 177 | var value = (int)binder.Field["FieldName"]; 178 | binder.Field["FieldName"] = newValue; 179 | ``` 180 | 181 | ### Call methods with `ref` & `out` arguments 182 | 183 | To call methods with `ref` & `out` arguments, you can't pass those arguments to the `Call` method of the late-binder directly, which is different from using the "dynamic" syntax. Instead, follow the instructions below. 184 | 185 | ```csharp 186 | // For example, if the definition of the "MethodName" static method is: 187 | 188 | // static void MethodName(int x, ref int y, out int z){ 189 | // z = x * y; 190 | // y = y + 1; 191 | // } 192 | 193 | // 1. Pack all of the arguments into an object array. 194 | var args = new object[] { 3, 4, default(int) }; 195 | 196 | // 2. Pass it to the 2nd argument of the "Call" method. 197 | binder.Call("MethodName", args); 198 | 199 | // 3. Then, the "args" array will be updated. 200 | // args[1] -> 5 201 | // args[2] -> 12 202 | ``` 203 | 204 | ## Note 205 | 206 | ### No using extension methods scenario 207 | 208 | If you feel these extension methods are dirty, you can choose no using these extension methods. 209 | 210 | Instead, you can use `LateBinder` and `DynamicBinder` class like the following code. 211 | 212 | ```csharp 213 | // Do not open namespace "Toolbelt.DynamicBinderExtension". 214 | using Toolbelt; 215 | ... 216 | // Instead, instantiate DynamicBinder or LateBinder objects with the "new" keyword. 217 | dynamic dynamicBinder = new DynamicBinder(obj); 218 | var retval = (int)dynamicBinder.MethodName(arg1, arg2); 219 | 220 | var lateBinder = new LateBinder(obj); 221 | var retval = (int)lateBinder.Call("MethodName", arg1, arg2); 222 | ``` 223 | 224 | ### "Reinventing the wheel" 225 | 226 | There are no less than 50 packages about reflection & private members accessing. 227 | 228 | - 🔍 https://www.nuget.org/packages?q=Tags%3A%22reflection%22 229 | 230 | But I couldn't find any packages with my favorite syntax and features :). 231 | 232 | So I decided to "reinvent the wheel" by my hand. 233 | 234 | 235 | ### Performance issue 236 | 237 | In this library, `DynamicBinder` and `LateBinder` may be much slower because their implementation uses the reflection API directory without any technics such as caches, compiling to delegations, compiling to expressions, etc. 238 | 239 | Therefore, I think there is plenty of room for improvement to faster, more high performance. 240 | 241 | If you prefer, you can fork this repository and improve it. 242 | 243 | ## Release Notes 244 | 245 | Release notes are [here.](https://github.com/jsakamoto/dynamicbinder/blob/master/RELEASE-NOTES.txt) 246 | 247 | ## Licence 248 | 249 | - [GNU Lesser General Public License v3.0 or later](https://github.com/jsakamoto/dynamicbinder/blob/master/LICENSE) 250 | -------------------------------------------------------------------------------- /RELEASE-NOTES.txt: -------------------------------------------------------------------------------- 1 | v.2.2.0 2 | - Enhance: Allow dynamic method call return values to be used as parameters in a method. 3 | 4 | v.2.1.0 5 | - Enhance: Add support for calling methods with "ref" and "out" arguments. 6 | 7 | v.2.0.0 8 | - Enhance: Add DynamicBinder.CreateInstance() and LateBinder.CreateInstance(). 9 | - Drop support for old .NET Framework versions. 10 | 11 | v.1.5.1 12 | - Enhance: Add .NET Standard 2.0 support. 13 | 14 | v.1.5 15 | - Enhance: Add "Object" property which expose the object that binding target. 16 | - Fix bug: Can not extract class type object by DynamicBinder. 17 | 18 | v.1.4 19 | - Fix bug: Can not retrieve class type properties by DynamicBinder. 20 | 21 | v.1.3 22 | - Add avility of exposing nested private object graph. 23 | 24 | v.1.2 25 | - Add avility of caching System.Reflection.MemberInfo (use SetCache(disctionary) method) 26 | - Add GetInfo(name) method on PropertyBinder and FieldBinder that returned System.Reflection.MemberInfo. 27 | 28 | v.1.1 29 | - Support base class members access. -------------------------------------------------------------------------------- /_dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/dynamicbinder/88208c45e2e5013dec3c8417c2828118338ff813/_dist/.gitkeep --------------------------------------------------------------------------------