1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Collections.ObjectModel; 7 using System.Diagnostics; 8 using System.Dynamic.Utils; 9 using System.Linq.Expressions; 10 using System.Reflection; 11 using System.Runtime.CompilerServices; 12 using AstUtils = System.Linq.Expressions.Utils; 13 using static System.Linq.Expressions.CachedReflectionInfo; 14 15 namespace System.Dynamic 16 { 17 /// <summary> 18 /// Provides a simple class that can be inherited from to create an object with dynamic behavior 19 /// at runtime. Subclasses can override the various binder methods (<see cref="TryGetMember"/>, 20 /// <see cref="TrySetMember"/>, <see cref="TryInvokeMember"/>, etc.) to provide custom behavior 21 /// that will be invoked at runtime. 22 /// 23 /// If a method is not overridden then the <see cref="DynamicObject"/> does not directly support 24 /// that behavior and the call site will determine how the binding should be performed. 25 /// </summary> 26 [Serializable] 27 public class DynamicObject : IDynamicMetaObjectProvider 28 { 29 /// <summary> 30 /// Enables derived types to create a new instance of <see cref="DynamicObject"/>. 31 /// </summary> 32 /// <remarks> 33 /// <see cref="DynamicObject"/> instances cannot be directly instantiated because they have no 34 /// implementation of dynamic behavior. 35 /// </remarks> DynamicObject()36 protected DynamicObject() 37 { 38 } 39 40 #region Public Virtual APIs 41 42 /// <summary> 43 /// Provides the implementation of getting a member. Derived classes can override 44 /// this method to customize behavior. When not overridden the call site requesting the 45 /// binder determines the behavior. 46 /// </summary> 47 /// <param name="binder">The binder provided by the call site.</param> 48 /// <param name="result">The result of the get operation.</param> 49 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 50 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryGetMember(GetMemberBinder binder, out object result)51 public virtual bool TryGetMember(GetMemberBinder binder, out object result) 52 { 53 result = null; 54 return false; 55 } 56 57 /// <summary> 58 /// Provides the implementation of setting a member. Derived classes can override 59 /// this method to customize behavior. When not overridden the call site requesting the 60 /// binder determines the behavior. 61 /// </summary> 62 /// <param name="binder">The binder provided by the call site.</param> 63 /// <param name="value">The value to set.</param> 64 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> TrySetMember(SetMemberBinder binder, object value)65 public virtual bool TrySetMember(SetMemberBinder binder, object value) => false; 66 67 /// <summary> 68 /// Provides the implementation of deleting a member. Derived classes can override 69 /// this method to customize behavior. When not overridden the call site requesting the 70 /// binder determines the behavior. 71 /// </summary> 72 /// <param name="binder">The binder provided by the call site.</param> 73 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> TryDeleteMember(DeleteMemberBinder binder)74 public virtual bool TryDeleteMember(DeleteMemberBinder binder) => false; 75 76 /// <summary> 77 /// Provides the implementation of calling a member. Derived classes can override 78 /// this method to customize behavior. When not overridden the call site requesting the 79 /// binder determines the behavior. 80 /// </summary> 81 /// <param name="binder">The binder provided by the call site.</param> 82 /// <param name="args">The arguments to be used for the invocation.</param> 83 /// <param name="result">The result of the invocation.</param> 84 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 85 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)86 public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) 87 { 88 result = null; 89 return false; 90 } 91 92 /// <summary> 93 /// Provides the implementation of converting the <see cref="DynamicObject"/> to another type. 94 /// Derived classes can override this method to customize behavior. When not overridden the 95 /// call site requesting the binder determines the behavior. 96 /// </summary> 97 /// <param name="binder">The binder provided by the call site.</param> 98 /// <param name="result">The result of the conversion.</param> 99 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 100 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryConvert(ConvertBinder binder, out object result)101 public virtual bool TryConvert(ConvertBinder binder, out object result) 102 { 103 result = null; 104 return false; 105 } 106 107 /// <summary> 108 /// Provides the implementation of creating an instance of the <see cref="DynamicObject"/>. 109 /// Derived classes can override this method to customize behavior. When not overridden the 110 /// call site requesting the binder determines the behavior. 111 /// </summary> 112 /// <param name="binder">The binder provided by the call site.</param> 113 /// <param name="args">The arguments used for creation.</param> 114 /// <param name="result">The created instance.</param> 115 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 116 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result)117 public virtual bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result) 118 { 119 result = null; 120 return false; 121 } 122 123 /// <summary> 124 /// Provides the implementation of invoking the <see cref="DynamicObject"/>. Derived classes can 125 /// override this method to customize behavior. When not overridden the call site requesting 126 /// the binder determines the behavior. 127 /// </summary> 128 /// <param name="binder">The binder provided by the call site.</param> 129 /// <param name="args">The arguments to be used for the invocation.</param> 130 /// <param name="result">The result of the invocation.</param> 131 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 132 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryInvoke(InvokeBinder binder, object[] args, out object result)133 public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result) 134 { 135 result = null; 136 return false; 137 } 138 139 /// <summary> 140 /// Provides the implementation of performing a binary operation. Derived classes can 141 /// override this method to customize behavior. When not overridden the call site requesting 142 /// the binder determines the behavior. 143 /// </summary> 144 /// <param name="binder">The binder provided by the call site.</param> 145 /// <param name="arg">The right operand for the operation.</param> 146 /// <param name="result">The result of the operation.</param> 147 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 148 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)149 public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) 150 { 151 result = null; 152 return false; 153 } 154 155 /// <summary> 156 /// Provides the implementation of performing a unary operation. Derived classes can 157 /// override this method to customize behavior. When not overridden the call site requesting 158 /// the binder determines the behavior. 159 /// </summary> 160 /// <param name="binder">The binder provided by the call site.</param> 161 /// <param name="result">The result of the operation.</param> 162 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 163 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryUnaryOperation(UnaryOperationBinder binder, out object result)164 public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result) 165 { 166 result = null; 167 return false; 168 } 169 170 /// <summary> 171 /// Provides the implementation of performing a get index operation. Derived classes can 172 /// override this method to customize behavior. When not overridden the call site requesting 173 /// the binder determines the behavior. 174 /// </summary> 175 /// <param name="binder">The binder provided by the call site.</param> 176 /// <param name="indexes">The indexes to be used.</param> 177 /// <param name="result">The result of the operation.</param> 178 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 179 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)180 public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) 181 { 182 result = null; 183 return false; 184 } 185 186 /// <summary> 187 /// Provides the implementation of performing a set index operation. Derived classes can 188 /// override this method to customize behavior. When not overridden the call site requesting 189 /// the binder determines the behavior. 190 /// </summary> 191 /// <param name="binder">The binder provided by the call site.</param> 192 /// <param name="indexes">The indexes to be used.</param> 193 /// <param name="value">The value to set.</param> 194 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> 195 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] TrySetIndex(SetIndexBinder binder, object[] indexes, object value)196 public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) => false; 197 198 /// <summary> 199 /// Provides the implementation of performing a delete index operation. Derived classes 200 /// can override this method to customize behavior. When not overridden the call site 201 /// requesting the binder determines the behavior. 202 /// </summary> 203 /// <param name="binder">The binder provided by the call site.</param> 204 /// <param name="indexes">The indexes to be deleted.</param> 205 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns> TryDeleteIndex(DeleteIndexBinder binder, object[] indexes)206 public virtual bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes) => false; 207 208 /// <summary> 209 /// Returns the enumeration of all dynamic member names. 210 /// </summary> 211 /// <returns>The list of dynamic member names.</returns> 212 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] GetDynamicMemberNames()213 public virtual IEnumerable<string> GetDynamicMemberNames() => Array.Empty<string>(); 214 215 #endregion 216 217 #region MetaDynamic 218 219 private sealed class MetaDynamic : DynamicMetaObject 220 { MetaDynamic(Expression expression, DynamicObject value)221 internal MetaDynamic(Expression expression, DynamicObject value) 222 : base(expression, BindingRestrictions.Empty, value) 223 { 224 } 225 GetDynamicMemberNames()226 public override IEnumerable<string> GetDynamicMemberNames() => Value.GetDynamicMemberNames(); 227 BindGetMember(GetMemberBinder binder)228 public override DynamicMetaObject BindGetMember(GetMemberBinder binder) 229 { 230 if (IsOverridden(DynamicObject_TryGetMember)) 231 { 232 return CallMethodWithResult( 233 DynamicObject_TryGetMember, 234 binder, 235 s_noArgs, 236 (MetaDynamic @this, GetMemberBinder b, DynamicMetaObject e) => b.FallbackGetMember(@this, e) 237 ); 238 } 239 240 return base.BindGetMember(binder); 241 } 242 BindSetMember(SetMemberBinder binder, DynamicMetaObject value)243 public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) 244 { 245 if (IsOverridden(DynamicObject_TrySetMember)) 246 { 247 DynamicMetaObject localValue = value; 248 249 return CallMethodReturnLast( 250 DynamicObject_TrySetMember, 251 binder, 252 s_noArgs, 253 value.Expression, 254 (MetaDynamic @this, SetMemberBinder b, DynamicMetaObject e) => b.FallbackSetMember(@this, localValue, e) 255 ); 256 } 257 258 return base.BindSetMember(binder, value); 259 } 260 BindDeleteMember(DeleteMemberBinder binder)261 public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) 262 { 263 if (IsOverridden(DynamicObject_TryDeleteMember)) 264 { 265 return CallMethodNoResult( 266 DynamicObject_TryDeleteMember, 267 binder, 268 s_noArgs, 269 (MetaDynamic @this, DeleteMemberBinder b, DynamicMetaObject e) => b.FallbackDeleteMember(@this, e) 270 ); 271 } 272 273 return base.BindDeleteMember(binder); 274 } 275 BindConvert(ConvertBinder binder)276 public override DynamicMetaObject BindConvert(ConvertBinder binder) 277 { 278 if (IsOverridden(DynamicObject_TryConvert)) 279 { 280 return CallMethodWithResult( 281 DynamicObject_TryConvert, 282 binder, 283 s_noArgs, 284 (MetaDynamic @this, ConvertBinder b, DynamicMetaObject e) => b.FallbackConvert(@this, e) 285 ); 286 } 287 288 return base.BindConvert(binder); 289 } 290 BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)291 public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) 292 { 293 // Generate a tree like: 294 // 295 // { 296 // object result; 297 // TryInvokeMember(payload, out result) 298 // ? result 299 // : TryGetMember(payload, out result) 300 // ? FallbackInvoke(result) 301 // : fallbackResult 302 // } 303 // 304 // Then it calls FallbackInvokeMember with this tree as the 305 // "error", giving the language the option of using this 306 // tree or doing .NET binding. 307 // 308 DynamicMetaObject call = BuildCallMethodWithResult( 309 DynamicObject_TryInvokeMember, 310 binder, 311 GetExpressions(args), 312 BuildCallMethodWithResult<GetMemberBinder>( 313 DynamicObject_TryGetMember, 314 new GetBinderAdapter(binder), 315 s_noArgs, 316 binder.FallbackInvokeMember(this, args, null), 317 (MetaDynamic @this, GetMemberBinder ignored, DynamicMetaObject e) => binder.FallbackInvoke(e, args, null) 318 ), 319 null 320 ); 321 322 return binder.FallbackInvokeMember(this, args, call); 323 } 324 BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args)325 public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) 326 { 327 if (IsOverridden(DynamicObject_TryCreateInstance)) 328 { 329 DynamicMetaObject[] localArgs = args; 330 331 return CallMethodWithResult( 332 DynamicObject_TryCreateInstance, 333 binder, 334 GetExpressions(args), 335 (MetaDynamic @this, CreateInstanceBinder b, DynamicMetaObject e) => b.FallbackCreateInstance(@this, localArgs, e) 336 ); 337 } 338 339 return base.BindCreateInstance(binder, args); 340 } 341 BindInvoke(InvokeBinder binder, DynamicMetaObject[] args)342 public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) 343 { 344 if (IsOverridden(DynamicObject_TryInvoke)) 345 { 346 DynamicMetaObject[] localArgs = args; 347 348 return CallMethodWithResult( 349 DynamicObject_TryInvoke, 350 binder, 351 GetExpressions(args), 352 (MetaDynamic @this, InvokeBinder b, DynamicMetaObject e) => b.FallbackInvoke(@this, localArgs, e) 353 ); 354 } 355 356 return base.BindInvoke(binder, args); 357 } 358 BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)359 public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) 360 { 361 if (IsOverridden(DynamicObject_TryBinaryOperation)) 362 { 363 DynamicMetaObject localArg = arg; 364 365 return CallMethodWithResult( 366 DynamicObject_TryBinaryOperation, 367 binder, 368 new[] { arg.Expression }, 369 (MetaDynamic @this, BinaryOperationBinder b, DynamicMetaObject e) => b.FallbackBinaryOperation(@this, localArg, e) 370 ); 371 } 372 373 return base.BindBinaryOperation(binder, arg); 374 } 375 BindUnaryOperation(UnaryOperationBinder binder)376 public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) 377 { 378 if (IsOverridden(DynamicObject_TryUnaryOperation)) 379 { 380 return CallMethodWithResult( 381 DynamicObject_TryUnaryOperation, 382 binder, 383 s_noArgs, 384 (MetaDynamic @this, UnaryOperationBinder b, DynamicMetaObject e) => b.FallbackUnaryOperation(@this, e) 385 ); 386 } 387 388 return base.BindUnaryOperation(binder); 389 } 390 BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes)391 public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) 392 { 393 if (IsOverridden(DynamicObject_TryGetIndex)) 394 { 395 DynamicMetaObject[] localIndexes = indexes; 396 397 return CallMethodWithResult( 398 DynamicObject_TryGetIndex, 399 binder, 400 GetExpressions(indexes), 401 (MetaDynamic @this, GetIndexBinder b, DynamicMetaObject e) => b.FallbackGetIndex(@this, localIndexes, e) 402 ); 403 } 404 405 return base.BindGetIndex(binder, indexes); 406 } 407 BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value)408 public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) 409 { 410 if (IsOverridden(DynamicObject_TrySetIndex)) 411 { 412 DynamicMetaObject[] localIndexes = indexes; 413 DynamicMetaObject localValue = value; 414 415 return CallMethodReturnLast( 416 DynamicObject_TrySetIndex, 417 binder, 418 GetExpressions(indexes), 419 value.Expression, 420 (MetaDynamic @this, SetIndexBinder b, DynamicMetaObject e) => b.FallbackSetIndex(@this, localIndexes, localValue, e) 421 ); 422 } 423 424 return base.BindSetIndex(binder, indexes, value); 425 } 426 BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes)427 public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) 428 { 429 if (IsOverridden(DynamicObject_TryDeleteIndex)) 430 { 431 DynamicMetaObject[] localIndexes = indexes; 432 433 return CallMethodNoResult( 434 DynamicObject_TryDeleteIndex, 435 binder, 436 GetExpressions(indexes), 437 (MetaDynamic @this, DeleteIndexBinder b, DynamicMetaObject e) => b.FallbackDeleteIndex(@this, localIndexes, e) 438 ); 439 } 440 441 return base.BindDeleteIndex(binder, indexes); 442 } 443 444 private delegate DynamicMetaObject Fallback<TBinder>(MetaDynamic @this, TBinder binder, DynamicMetaObject errorSuggestion); 445 446 private static readonly Expression[] s_noArgs = new Expression[0]; // used in reference comparison, requires unique object identity 447 GetConvertedArgs(params Expression[] args)448 private static ReadOnlyCollection<Expression> GetConvertedArgs(params Expression[] args) 449 { 450 var paramArgs = new Expression[args.Length]; 451 452 for (int i = 0; i < args.Length; i++) 453 { 454 paramArgs[i] = Expression.Convert(args[i], typeof(object)); 455 } 456 457 return new TrueReadOnlyCollection<Expression>(paramArgs); 458 } 459 460 /// <summary> 461 /// Helper method for generating expressions that assign byRef call 462 /// parameters back to their original variables. 463 /// </summary> ReferenceArgAssign(Expression callArgs, Expression[] args)464 private static Expression ReferenceArgAssign(Expression callArgs, Expression[] args) 465 { 466 ReadOnlyCollectionBuilder<Expression> block = null; 467 468 for (int i = 0; i < args.Length; i++) 469 { 470 ParameterExpression variable = args[i] as ParameterExpression; 471 ContractUtils.Requires(variable != null, nameof(args)); 472 473 if (variable.IsByRef) 474 { 475 if (block == null) 476 block = new ReadOnlyCollectionBuilder<Expression>(); 477 478 block.Add( 479 Expression.Assign( 480 variable, 481 Expression.Convert( 482 Expression.ArrayIndex( 483 callArgs, 484 AstUtils.Constant(i) 485 ), 486 variable.Type 487 ) 488 ) 489 ); 490 } 491 } 492 493 if (block != null) 494 return Expression.Block(block); 495 else 496 return AstUtils.Empty; 497 } 498 499 /// <summary> 500 /// Helper method for generating arguments for calling methods 501 /// on DynamicObject. parameters is either a list of ParameterExpressions 502 /// to be passed to the method as an object[], or NoArgs to signify that 503 /// the target method takes no object[] parameter. 504 /// </summary> 505 private static Expression[] BuildCallArgs<TBinder>(TBinder binder, Expression[] parameters, Expression arg0, Expression arg1) 506 where TBinder : DynamicMetaObjectBinder 507 { 508 if (!object.ReferenceEquals(parameters, s_noArgs)) 509 return arg1 != null ? new Expression[] { Constant(binder), arg0, arg1 } : new Expression[] { Constant(binder), arg0 }; 510 else 511 return arg1 != null ? new Expression[] { Constant(binder), arg1 } : new Expression[] { Constant(binder) }; 512 } 513 Constant(TBinder binder)514 private static ConstantExpression Constant<TBinder>(TBinder binder) 515 { 516 return Expression.Constant(binder, typeof(TBinder)); 517 } 518 519 /// <summary> 520 /// Helper method for generating a MetaObject which calls a 521 /// specific method on Dynamic that returns a result 522 /// </summary> 523 private DynamicMetaObject CallMethodWithResult<TBinder>(MethodInfo method, TBinder binder, Expression[] args, Fallback<TBinder> fallback) 524 where TBinder : DynamicMetaObjectBinder 525 { 526 return CallMethodWithResult(method, binder, args, fallback, null); 527 } 528 529 /// <summary> 530 /// Helper method for generating a MetaObject which calls a 531 /// specific method on Dynamic that returns a result 532 /// </summary> 533 private DynamicMetaObject CallMethodWithResult<TBinder>(MethodInfo method, TBinder binder, Expression[] args, Fallback<TBinder> fallback, Fallback<TBinder> fallbackInvoke) 534 where TBinder : DynamicMetaObjectBinder 535 { 536 // 537 // First, call fallback to do default binding 538 // This produces either an error or a call to a .NET member 539 // 540 DynamicMetaObject fallbackResult = fallback(this, binder, null); 541 542 DynamicMetaObject callDynamic = BuildCallMethodWithResult(method, binder, args, fallbackResult, fallbackInvoke); 543 544 // 545 // Now, call fallback again using our new MO as the error 546 // When we do this, one of two things can happen: 547 // 1. Binding will succeed, and it will ignore our call to 548 // the dynamic method, OR 549 // 2. Binding will fail, and it will use the MO we created 550 // above. 551 // 552 return fallback(this, binder, callDynamic); 553 } 554 555 /// <summary> 556 /// Helper method for generating a MetaObject which calls a 557 /// specific method on DynamicObject that returns a result. 558 /// 559 /// args is either an array of arguments to be passed 560 /// to the method as an object[] or NoArgs to signify that 561 /// the target method takes no parameters. 562 /// </summary> 563 private DynamicMetaObject BuildCallMethodWithResult<TBinder>(MethodInfo method, TBinder binder, Expression[] args, DynamicMetaObject fallbackResult, Fallback<TBinder> fallbackInvoke) 564 where TBinder : DynamicMetaObjectBinder 565 { 566 if (!IsOverridden(method)) 567 { 568 return fallbackResult; 569 } 570 571 // 572 // Build a new expression like: 573 // { 574 // object result; 575 // TryGetMember(payload, out result) ? fallbackInvoke(result) : fallbackResult 576 // } 577 // 578 ParameterExpression result = Expression.Parameter(typeof(object), null); 579 ParameterExpression callArgs = method != DynamicObject_TryBinaryOperation ? Expression.Parameter(typeof(object[]), null) : Expression.Parameter(typeof(object), null); 580 ReadOnlyCollection<Expression> callArgsValue = GetConvertedArgs(args); 581 582 var resultMO = new DynamicMetaObject(result, BindingRestrictions.Empty); 583 584 // Need to add a conversion if calling TryConvert 585 if (binder.ReturnType != typeof(object)) 586 { 587 Debug.Assert(binder is ConvertBinder && fallbackInvoke == null); 588 589 UnaryExpression convert = Expression.Convert(resultMO.Expression, binder.ReturnType); 590 // will always be a cast or unbox 591 Debug.Assert(convert.Method == null); 592 593 // Prepare a good exception message in case the convert will fail 594 string convertFailed = System.Linq.Expressions.Strings.DynamicObjectResultNotAssignable( 595 "{0}", 596 this.Value.GetType(), 597 binder.GetType(), 598 binder.ReturnType 599 ); 600 601 Expression condition; 602 // If the return type can not be assigned null then just check for type assignability otherwise allow null. 603 if (binder.ReturnType.IsValueType && Nullable.GetUnderlyingType(binder.ReturnType) == null) 604 { 605 condition = Expression.TypeIs(resultMO.Expression, binder.ReturnType); 606 } 607 else 608 { 609 condition = Expression.OrElse( 610 Expression.Equal(resultMO.Expression, AstUtils.Null), 611 Expression.TypeIs(resultMO.Expression, binder.ReturnType)); 612 } 613 614 Expression checkedConvert = Expression.Condition( 615 condition, 616 convert, 617 Expression.Throw( 618 Expression.New( 619 InvalidCastException_Ctor_String, 620 new TrueReadOnlyCollection<Expression>( 621 Expression.Call( 622 String_Format_String_ObjectArray, 623 Expression.Constant(convertFailed), 624 Expression.NewArrayInit( 625 typeof(object), 626 new TrueReadOnlyCollection<Expression>( 627 Expression.Condition( 628 Expression.Equal(resultMO.Expression, AstUtils.Null), 629 Expression.Constant("null"), 630 Expression.Call( 631 resultMO.Expression, 632 Object_GetType 633 ), 634 typeof(object) 635 ) 636 ) 637 ) 638 ) 639 ) 640 ), 641 binder.ReturnType 642 ), 643 binder.ReturnType 644 ); 645 646 resultMO = new DynamicMetaObject(checkedConvert, resultMO.Restrictions); 647 } 648 649 if (fallbackInvoke != null) 650 { 651 resultMO = fallbackInvoke(this, binder, resultMO); 652 } 653 654 var callDynamic = new DynamicMetaObject( 655 Expression.Block( 656 new TrueReadOnlyCollection<ParameterExpression>(result, callArgs), 657 new TrueReadOnlyCollection<Expression>( 658 method != DynamicObject_TryBinaryOperation ? Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)) : Expression.Assign(callArgs, callArgsValue[0]), 659 Expression.Condition( 660 Expression.Call( 661 GetLimitedSelf(), 662 method, 663 BuildCallArgs( 664 binder, 665 args, 666 callArgs, 667 result 668 ) 669 ), 670 Expression.Block( 671 method != DynamicObject_TryBinaryOperation ? ReferenceArgAssign(callArgs, args) : AstUtils.Empty, 672 resultMO.Expression 673 ), 674 fallbackResult.Expression, 675 binder.ReturnType 676 ) 677 ) 678 ), 679 GetRestrictions().Merge(resultMO.Restrictions).Merge(fallbackResult.Restrictions) 680 ); 681 return callDynamic; 682 } 683 684 /// <summary> 685 /// Helper method for generating a MetaObject which calls a 686 /// specific method on Dynamic, but uses one of the arguments for 687 /// the result. 688 /// 689 /// args is either an array of arguments to be passed 690 /// to the method as an object[] or NoArgs to signify that 691 /// the target method takes no parameters. 692 /// </summary> 693 private DynamicMetaObject CallMethodReturnLast<TBinder>(MethodInfo method, TBinder binder, Expression[] args, Expression value, Fallback<TBinder> fallback) 694 where TBinder : DynamicMetaObjectBinder 695 { 696 // 697 // First, call fallback to do default binding 698 // This produces either an error or a call to a .NET member 699 // 700 DynamicMetaObject fallbackResult = fallback(this, binder, null); 701 702 // 703 // Build a new expression like: 704 // { 705 // object result; 706 // TrySetMember(payload, result = value) ? result : fallbackResult 707 // } 708 // 709 710 ParameterExpression result = Expression.Parameter(typeof(object), null); 711 ParameterExpression callArgs = Expression.Parameter(typeof(object[]), null); 712 ReadOnlyCollection<Expression> callArgsValue = GetConvertedArgs(args); 713 714 var callDynamic = new DynamicMetaObject( 715 Expression.Block( 716 new TrueReadOnlyCollection<ParameterExpression>(result, callArgs), 717 new TrueReadOnlyCollection<Expression>( 718 Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)), 719 Expression.Condition( 720 Expression.Call( 721 GetLimitedSelf(), 722 method, 723 BuildCallArgs( 724 binder, 725 args, 726 callArgs, 727 Expression.Assign(result, Expression.Convert(value, typeof(object))) 728 ) 729 ), 730 Expression.Block( 731 ReferenceArgAssign(callArgs, args), 732 result 733 ), 734 fallbackResult.Expression, 735 typeof(object) 736 ) 737 ) 738 ), 739 GetRestrictions().Merge(fallbackResult.Restrictions) 740 ); 741 742 // 743 // Now, call fallback again using our new MO as the error 744 // When we do this, one of two things can happen: 745 // 1. Binding will succeed, and it will ignore our call to 746 // the dynamic method, OR 747 // 2. Binding will fail, and it will use the MO we created 748 // above. 749 // 750 return fallback(this, binder, callDynamic); 751 } 752 753 /// <summary> 754 /// Helper method for generating a MetaObject which calls a 755 /// specific method on Dynamic, but uses one of the arguments for 756 /// the result. 757 /// 758 /// args is either an array of arguments to be passed 759 /// to the method as an object[] or NoArgs to signify that 760 /// the target method takes no parameters. 761 /// </summary> 762 private DynamicMetaObject CallMethodNoResult<TBinder>(MethodInfo method, TBinder binder, Expression[] args, Fallback<TBinder> fallback) 763 where TBinder : DynamicMetaObjectBinder 764 { 765 // 766 // First, call fallback to do default binding 767 // This produces either an error or a call to a .NET member 768 // 769 DynamicMetaObject fallbackResult = fallback(this, binder, null); 770 ParameterExpression callArgs = Expression.Parameter(typeof(object[]), null); 771 ReadOnlyCollection<Expression> callArgsValue = GetConvertedArgs(args); 772 773 // 774 // Build a new expression like: 775 // if (TryDeleteMember(payload)) { } else { fallbackResult } 776 // 777 var callDynamic = new DynamicMetaObject( 778 Expression.Block( 779 new TrueReadOnlyCollection<ParameterExpression>(callArgs), 780 new TrueReadOnlyCollection<Expression>( 781 Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)), 782 Expression.Condition( 783 Expression.Call( 784 GetLimitedSelf(), 785 method, 786 BuildCallArgs( 787 binder, 788 args, 789 callArgs, 790 null 791 ) 792 ), 793 Expression.Block( 794 ReferenceArgAssign(callArgs, args), 795 AstUtils.Empty 796 ), 797 fallbackResult.Expression, 798 typeof(void) 799 ) 800 ) 801 ), 802 GetRestrictions().Merge(fallbackResult.Restrictions) 803 ); 804 805 // 806 // Now, call fallback again using our new MO as the error 807 // When we do this, one of two things can happen: 808 // 1. Binding will succeed, and it will ignore our call to 809 // the dynamic method, OR 810 // 2. Binding will fail, and it will use the MO we created 811 // above. 812 // 813 return fallback(this, binder, callDynamic); 814 } 815 816 /// <summary> 817 /// Checks if the derived type has overridden the specified method. If there is no 818 /// implementation for the method provided then Dynamic falls back to the base class 819 /// behavior which lets the call site determine how the binder is performed. 820 /// </summary> IsOverridden(MethodInfo method)821 private bool IsOverridden(MethodInfo method) 822 { 823 MemberInfo[] methods = Value.GetType().GetMember(method.Name, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance); 824 825 foreach (MethodInfo mi in methods) 826 { 827 if (mi.DeclaringType != typeof(DynamicObject) && mi.GetBaseDefinition() == method) 828 { 829 return true; 830 } 831 } 832 833 return false; 834 } 835 836 /// <summary> 837 /// Returns a Restrictions object which includes our current restrictions merged 838 /// with a restriction limiting our type 839 /// </summary> GetRestrictions()840 private BindingRestrictions GetRestrictions() 841 { 842 Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty"); 843 844 return BindingRestrictions.GetTypeRestriction(this); 845 } 846 847 /// <summary> 848 /// Returns our Expression converted to DynamicObject 849 /// </summary> GetLimitedSelf()850 private Expression GetLimitedSelf() 851 { 852 // Convert to DynamicObject rather than LimitType, because 853 // the limit type might be non-public. 854 if (TypeUtils.AreEquivalent(Expression.Type, typeof(DynamicObject))) 855 { 856 return Expression; 857 } 858 return Expression.Convert(Expression, typeof(DynamicObject)); 859 } 860 861 private new DynamicObject Value => (DynamicObject)base.Value; 862 863 // It is okay to throw NotSupported from this binder. This object 864 // is only used by DynamicObject.GetMember--it is not expected to 865 // (and cannot) implement binding semantics. It is just so the DO 866 // can use the Name and IgnoreCase properties. 867 private sealed class GetBinderAdapter : GetMemberBinder 868 { GetBinderAdapter(InvokeMemberBinder binder)869 internal GetBinderAdapter(InvokeMemberBinder binder) 870 : base(binder.Name, binder.IgnoreCase) 871 { 872 } 873 FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)874 public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) 875 { 876 throw new NotSupportedException(); 877 } 878 } 879 } 880 881 #endregion 882 883 #region IDynamicMetaObjectProvider Members 884 885 /// <summary> 886 /// Returns the <see cref="DynamicMetaObject" /> responsible for binding operations performed on this object, 887 /// using the virtual methods provided by this class. 888 /// </summary> 889 /// <param name="parameter">The expression tree representation of the runtime value.</param> 890 /// <returns> 891 /// The <see cref="DynamicMetaObject" /> to bind this object. The object can be encapsulated inside of another 892 /// <see cref="DynamicMetaObject"/> to provide custom behavior for individual actions. 893 /// </returns> GetMetaObject(Expression parameter)894 public virtual DynamicMetaObject GetMetaObject(Expression parameter) => new MetaDynamic(parameter, this); 895 896 #endregion 897 } 898 } 899