1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
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.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
17 using Microsoft.Scripting.Ast;
19 using System.Linq.Expressions;
22 using System.Diagnostics;
23 using System.Dynamic.Utils;
24 using System.Reflection;
25 using System.Runtime.CompilerServices;
27 namespace System.Dynamic {
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.
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.
37 public class DynamicObject : IDynamicMetaObjectProvider {
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.
43 protected DynamicObject() {
46 #region Public Virtual APIs
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
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.
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) {
215 /// Returns the enumeration of all dynamic member names.
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];
226 private sealed class MetaDynamic : DynamicMetaObject {
228 internal MetaDynamic(Expression expression, DynamicObject value)
229 : base(expression, BindingRestrictions.Empty, value) {
232 public override System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames()
234 return Value.GetDynamicMemberNames();
237 public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
238 if (IsOverridden("TryGetMember")) {
239 return CallMethodWithResult("TryGetMember", binder, NoArgs, (e) => binder.FallbackGetMember(this, e));
242 return base.BindGetMember(binder);
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));
250 return base.BindSetMember(binder, value);
253 public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) {
254 if (IsOverridden("TryDeleteMember")) {
255 return CallMethodNoResult("TryDeleteMember", binder, NoArgs, (e) => binder.FallbackDeleteMember(this, e));
258 return base.BindDeleteMember(binder);
261 public override DynamicMetaObject BindConvert(ConvertBinder binder) {
262 if (IsOverridden("TryConvert")) {
263 return CallMethodWithResult("TryConvert", binder, NoArgs, (e) => binder.FallbackConvert(this, e));
266 return base.BindConvert(binder);
269 public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
270 // Generate a tree like:
274 // TryInvokeMember(payload, out result)
276 // : TryGetMember(payload, out result)
277 // ? FallbackInvoke(result)
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.
285 Fallback fallback = e => binder.FallbackInvokeMember(this, args, e);
287 var call = BuildCallMethodWithResult(
290 DynamicMetaObject.GetExpressions(args),
291 BuildCallMethodWithResult(
293 new GetBinderAdapter(binder),
296 (e) => binder.FallbackInvoke(e, args, null)
301 return fallback(call);
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));
310 return base.BindCreateInstance(binder, args);
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));
318 return base.BindInvoke(binder, args);
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));
326 return base.BindBinaryOperation(binder, arg);
329 public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) {
330 if (IsOverridden("TryUnaryOperation")) {
331 return CallMethodWithResult("TryUnaryOperation", binder, NoArgs, (e) => binder.FallbackUnaryOperation(this, e));
334 return base.BindUnaryOperation(binder);
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));
342 return base.BindGetIndex(binder, indexes);
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));
350 return base.BindSetIndex(binder, indexes, value);
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));
358 return base.BindDeleteIndex(binder, indexes);
361 private delegate DynamicMetaObject Fallback(DynamicMetaObject errorSuggestion);
363 private readonly static Expression[] NoArgs = new Expression[0];
365 private static Expression[] GetConvertedArgs(params Expression[] args) {
366 ReadOnlyCollectionBuilder<Expression> paramArgs = new ReadOnlyCollectionBuilder<Expression>(args.Length);
368 for (int i = 0; i < args.Length; i++) {
369 paramArgs.Add(Expression.Convert(args[i], typeof(object)));
372 return paramArgs.ToArray();
376 /// Helper method for generating expressions that assign byRef call
377 /// parameters back to their original variables
379 private static Expression ReferenceArgAssign(Expression callArgs, Expression[] args) {
380 ReadOnlyCollectionBuilder<Expression> block = null;
382 for (int i = 0; i < args.Length; i++) {
383 ContractUtils.Requires(args[i] is ParameterExpression);
384 if (((ParameterExpression)args[i]).IsByRef) {
386 block = new ReadOnlyCollectionBuilder<Expression>();
392 Expression.ArrayIndex(
394 Expression.Constant(i)
404 return Expression.Block(block);
406 return Expression.Empty();
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.
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 };
419 return arg1 != null ? new Expression[] { Constant(binder), arg1 } : new Expression[] { Constant(binder) };
422 private static ConstantExpression Constant(DynamicMetaObjectBinder binder) {
423 Type t = binder.GetType();
424 while (!t.IsVisible) {
427 return Expression.Constant(binder, t);
431 /// Helper method for generating a MetaObject which calls a
432 /// specific method on Dynamic that returns a result
434 private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
435 return CallMethodWithResult(methodName, binder, args, fallback, null);
439 /// Helper method for generating a MetaObject which calls a
440 /// specific method on Dynamic that returns a result
442 private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback, Fallback fallbackInvoke) {
444 // First, call fallback to do default binding
445 // This produces either an error or a call to a .NET member
447 DynamicMetaObject fallbackResult = fallback(null);
449 var callDynamic = BuildCallMethodWithResult(methodName, binder, args, fallbackResult, fallbackInvoke);
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
459 return fallback(callDynamic);
463 /// Helper method for generating a MetaObject which calls a
464 /// specific method on DynamicObject that returns a result.
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.
470 private DynamicMetaObject BuildCallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, DynamicMetaObject fallbackResult, Fallback fallbackInvoke) {
471 if (!IsOverridden(methodName)) {
472 return fallbackResult;
476 // Build a new expression like:
479 // TryGetMember(payload, out result) ? fallbackInvoke(result) : fallbackResult
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);
486 var resultMO = new DynamicMetaObject(result, BindingRestrictions.Empty);
488 // Need to add a conversion if calling TryConvert
489 if (binder.ReturnType != typeof(object)) {
490 Debug.Assert(binder is ConvertBinder && fallbackInvoke == null);
492 var convert = Expression.Convert(resultMO.Expression, binder.ReturnType);
493 // will always be a cast or unbox
494 Debug.Assert(convert.Method == null);
496 // Prepare a good exception message in case the convert will fail
497 string convertFailed = Strings.DynamicObjectResultNotAssignable(
499 this.Value.GetType(),
504 var checkedConvert = Expression.Condition(
505 Expression.TypeIs(resultMO.Expression, binder.ReturnType),
508 Expression.New(typeof(InvalidCastException).GetConstructor(new Type[]{typeof(string)}),
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"),
517 typeof(object).GetMethod("GetType")
528 resultMO = new DynamicMetaObject(checkedConvert, resultMO.Restrictions);
531 if (fallbackInvoke != null) {
532 resultMO = fallbackInvoke(resultMO);
535 var callDynamic = new DynamicMetaObject(
537 new[] { result, callArgs },
538 methodName != "TryBinaryOperation" ? Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)) : Expression.Assign(callArgs, callArgsValue[0]),
539 Expression.Condition(
542 typeof(DynamicObject).GetMethod(methodName),
551 methodName != "TryBinaryOperation" ? ReferenceArgAssign(callArgs, args) : Expression.Empty(),
554 fallbackResult.Expression,
558 GetRestrictions().Merge(resultMO.Restrictions).Merge(fallbackResult.Restrictions)
565 /// Helper method for generating a MetaObject which calls a
566 /// specific method on Dynamic, but uses one of the arguments for
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.
573 private DynamicMetaObject CallMethodReturnLast(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Expression value, Fallback fallback) {
575 // First, call fallback to do default binding
576 // This produces either an error or a call to a .NET member
578 DynamicMetaObject fallbackResult = fallback(null);
581 // Build a new expression like:
584 // TrySetMember(payload, result = value) ? result : fallbackResult
588 var result = Expression.Parameter(typeof(object), null);
589 var callArgs = Expression.Parameter(typeof(object[]), null);
590 var callArgsValue = GetConvertedArgs(args);
592 var callDynamic = new DynamicMetaObject(
594 new[] { result, callArgs },
595 Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)),
596 Expression.Condition(
599 typeof(DynamicObject).GetMethod(methodName),
604 Expression.Assign(result, Expression.Convert(value, typeof(object)))
608 ReferenceArgAssign(callArgs, args),
611 fallbackResult.Expression,
615 GetRestrictions().Merge(fallbackResult.Restrictions)
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
626 return fallback(callDynamic);
631 /// Helper method for generating a MetaObject which calls a
632 /// specific method on Dynamic, but uses one of the arguments for
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.
639 private DynamicMetaObject CallMethodNoResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
641 // First, call fallback to do default binding
642 // This produces either an error or a call to a .NET member
644 DynamicMetaObject fallbackResult = fallback(null);
645 var callArgs = Expression.Parameter(typeof(object[]), null);
646 var callArgsValue = GetConvertedArgs(args);
649 // Build a new expression like:
650 // if (TryDeleteMember(payload)) { } else { fallbackResult }
652 var callDynamic = new DynamicMetaObject(
655 Expression.Assign(callArgs, Expression.NewArrayInit(typeof(object), callArgsValue)),
656 Expression.Condition(
659 typeof(DynamicObject).GetMethod(methodName),
668 ReferenceArgAssign(callArgs, args),
671 fallbackResult.Expression,
675 GetRestrictions().Merge(fallbackResult.Restrictions)
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
686 return fallback(callDynamic);
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.
694 private bool IsOverridden(string method) {
695 var methods = Value.GetType().GetMember(method, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance);
697 foreach (MethodInfo mi in methods) {
698 if (mi.DeclaringType != typeof(DynamicObject) && mi.GetBaseDefinition().DeclaringType == typeof(DynamicObject)) {
707 /// Returns a Restrictions object which includes our current restrictions merged
708 /// with a restriction limiting our type
710 private BindingRestrictions GetRestrictions() {
711 Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty");
713 return BindingRestrictions.GetTypeRestriction(this);
717 /// Returns our Expression converted to DynamicObject
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))) {
725 return Expression.Convert(Expression, typeof(DynamicObject));
728 private new DynamicObject Value {
730 return (DynamicObject)base.Value;
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) {
743 public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) {
744 throw new NotSupportedException();
751 #region IDynamicMetaObjectProvider Members
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.
758 public virtual DynamicMetaObject GetMetaObject(Expression parameter) {
759 return new MetaDynamic(parameter, this);