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