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