Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Core / Microsoft / Scripting / Actions / DynamicObject.cs / 1407647 / DynamicObject.cs

    /* **************************************************************************** 
 *
 * Copyright (c) Microsoft Corporation.
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/ 

using System.Diagnostics; 
using System.Dynamic.Utils; 
using System.Linq.Expressions;
using System.Reflection; 

namespace System.Dynamic {
    /// 
    /// Provides a simple class that can be inherited from to create an object with dynamic behavior 
    /// at runtime.  Subclasses can override the various binder methods (GetMember, SetMember, Call, etc...)
    /// to provide custom behavior that will be invoked at runtime. 
    /// 
    /// If a method is not overridden then the DynamicObject does not directly support that behavior and
    /// the call site will determine how the binding should be performed. 
    /// 
    public class DynamicObject : IDynamicMetaObjectProvider {

        ///  
        /// Enables derived types to create a new instance of DynamicObject.  DynamicObject instances cannot be
        /// directly instantiated because they have no implementation of dynamic behavior. 
        ///  
        protected DynamicObject() {
        } 

        #region Public Virtual APIs

        ///  
        /// Provides the implementation of getting a member.  Derived classes can override
        /// this method to customize behavior.  When not overridden the call site requesting the 
        /// binder determines the behavior. 
        /// 
        /// The binder provided by the call site. 
        /// The result of the get operation.
        /// true if the operation is complete, false if the call site should determine behavior.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
        public virtual bool TryGetMember(GetMemberBinder binder, out object result) { 
            result = null;
            return false; 
        } 

        ///  
        /// Provides the implementation of setting a member.  Derived classes can override
        /// this method to customize behavior.  When not overridden the call site requesting the
        /// binder determines the behavior.
        ///  
        /// The binder provided by the call site.
        /// The value to set. 
        /// true if the operation is complete, false if the call site should determine behavior. 
        public virtual bool TrySetMember(SetMemberBinder binder, object value) {
            return false; 
        }

        /// 
        /// Provides the implementation of deleting a member.  Derived classes can override 
        /// this method to customize behavior.  When not overridden the call site requesting the
        /// binder determines the behavior. 
        ///  
        /// The binder provided by the call site.
        /// true if the operation is complete, false if the call site should determine behavior. 
        public virtual bool TryDeleteMember(DeleteMemberBinder binder) {
            return false;
        }
 
        /// 
        /// Provides the implementation of calling a member.  Derived classes can override 
        /// this method to customize behavior.  When not overridden the call site requesting the 
        /// binder determines the behavior.
        ///  
        /// The binder provided by the call site.
        /// The arguments to be used for the invocation.
        /// The result of the invocation.
        /// true if the operation is complete, false if the call site should determine behavior. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
        public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { 
            result = null; 
            return false;
        } 

        /// 
        /// Provides the implementation of converting the DynamicObject to another type.  Derived classes
        /// can override this method to customize behavior.  When not overridden the call site 
        /// requesting the binder determines the behavior.
        ///  
        /// The binder provided by the call site. 
        /// The result of the conversion.
        /// true if the operation is complete, false if the call site should determine behavior. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
        public virtual bool TryConvert(ConvertBinder binder, out object result) {
            result = null;
            return false; 
        }
 
        ///  
        /// Provides the implementation of creating an instance of the DynamicObject.  Derived classes
        /// can override this method to customize behavior.  When not overridden the call site requesting 
        /// the binder determines the behavior.
        /// 
        /// The binder provided by the call site.
        /// The arguments used for creation. 
        /// The created instance.
        /// true if the operation is complete, false if the call site should determine behavior. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] 
        public virtual bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result) {
            result = null; 
            return false;
        }

        ///  
        /// Provides the implementation of invoking the DynamicObject.  Derived classes can
        /// override this method to customize behavior.  When not overridden the call site requesting 
        /// the binder determines the behavior. 
        /// 
        /// The binder provided by the call site. 
        /// The arguments to be used for the invocation.
        /// The result of the invocation.
        /// true if the operation is complete, false if the call site should determine behavior.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] 
        public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result) {
            result = null; 
            return false; 
        }
 
        /// 
        /// Provides the implementation of performing a binary operation.  Derived classes can
        /// override this method to customize behavior.  When not overridden the call site requesting
        /// the binder determines the behavior. 
        /// 
        /// The binder provided by the call site. 
        /// The right operand for the operation. 
        /// The result of the operation.
        /// true if the operation is complete, false if the call site should determine behavior. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
        public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) {
            result = null;
            return false; 
        }
 
        ///  
        /// Provides the implementation of performing a unary operation.  Derived classes can
        /// override this method to customize behavior.  When not overridden the call site requesting 
        /// the binder determines the behavior.
        /// 
        /// The binder provided by the call site.
        /// The result of the operation. 
        /// true if the operation is complete, false if the call site should determine behavior.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] 
        public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result) { 
            result = null;
            return false; 
        }

        /// 
        /// Provides the implementation of performing a get index operation.  Derived classes can 
        /// override this method to customize behavior.  When not overridden the call site requesting
        /// the binder determines the behavior. 
        ///  
        /// The binder provided by the call site.
        /// The indexes to be used. 
        /// The result of the operation.
        /// true if the operation is complete, false if the call site should determine behavior.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
        public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { 
            result = null;
            return false; 
        } 

        ///  
        /// Provides the implementation of performing a set index operation.  Derived classes can
        /// override this method to custmize behavior.  When not overridden the call site requesting
        /// the binder determines the behavior.
        ///  
        /// The binder provided by the call site.
        /// The indexes to be used. 
        /// The value to set. 
        /// true if the operation is complete, false if the call site should determine behavior.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")] 
        public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) {
            return false;
        }
 
        /// 
        /// Provides the implementation of performing a delete index operation.  Derived classes 
        /// can override this method to custmize behavior.  When not overridden the call site 
        /// requesting the binder determines the behavior.
        ///  
        /// The binder provided by the call site.
        /// The indexes to be deleted.
        /// true if the operation is complete, false if the call site should determine behavior.
        public virtual bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes) { 
            return false;
        } 
 
        /// 
        /// Returns the enumeration of all dynamic member names. 
        /// 
        /// The list of dynamic member names.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public virtual System.Collections.Generic.IEnumerable GetDynamicMemberNames() { 
            return new string[0];
        } 
        #endregion 

        #region MetaDynamic 

        private sealed class MetaDynamic : DynamicMetaObject {

            internal MetaDynamic(Expression expression, DynamicObject value) 
                : base(expression, BindingRestrictions.Empty, value) {
            } 
 
            public override System.Collections.Generic.IEnumerable GetDynamicMemberNames()
            { 
                return Value.GetDynamicMemberNames();
            }

            public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { 
                if (IsOverridden("TryGetMember")) {
                    return CallMethodWithResult("TryGetMember", binder, NoArgs, (e) => binder.FallbackGetMember(this, e)); 
                } 

                return base.BindGetMember(binder); 
            }

            public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
                if (IsOverridden("TrySetMember")) { 
                    return CallMethodReturnLast("TrySetMember", binder, GetArgs(value), (e) => binder.FallbackSetMember(this, value, e));
                } 
 
                return base.BindSetMember(binder, value);
            } 

            public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) {
                if (IsOverridden("TryDeleteMember")) {
                    return CallMethodNoResult("TryDeleteMember", binder, NoArgs, (e) => binder.FallbackDeleteMember(this, e)); 
                }
 
                return base.BindDeleteMember(binder); 
            }
 
            public override DynamicMetaObject BindConvert(ConvertBinder binder) {
                if (IsOverridden("TryConvert")) {
                    return CallMethodWithResult("TryConvert", binder, NoArgs, (e) => binder.FallbackConvert(this, e));
                } 

                return base.BindConvert(binder); 
            } 

            public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { 
                // Generate a tree like:
                //
                // {
                //   object result; 
                //   TryInvokeMember(payload, out result)
                //      ? result 
                //      : TryGetMember(payload, out result) 
                //          ? FallbackInvoke(result)
                //          : fallbackResult 
                // }
                //
                // Then it calls FallbackInvokeMember with this tree as the
                // "error", giving the language the option of using this 
                // tree or doing .NET binding.
                // 
                Fallback fallback = e => binder.FallbackInvokeMember(this, args, e); 

                var call = BuildCallMethodWithResult( 
                    "TryInvokeMember",
                    binder,
                    GetArgArray(args),
                    BuildCallMethodWithResult( 
                        "TryGetMember",
                        new GetBinderAdapter(binder), 
                        NoArgs, 
                        fallback(null),
                        (e) => binder.FallbackInvoke(e, args, null) 
                    ),
                    null
                );
 
                return fallback(call);
            } 
 

            public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) { 
                if (IsOverridden("TryCreateInstance")) {
                    return CallMethodWithResult("TryCreateInstance", binder, GetArgArray(args), (e) => binder.FallbackCreateInstance(this, args, e));
                }
 
                return base.BindCreateInstance(binder, args);
            } 
 
            public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) {
                if (IsOverridden("TryInvoke")) { 
                    return CallMethodWithResult("TryInvoke", binder, GetArgArray(args), (e) => binder.FallbackInvoke(this, args, e));
                }

                return base.BindInvoke(binder, args); 
            }
 
            public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) { 
                if (IsOverridden("TryBinaryOperation")) {
                    return CallMethodWithResult("TryBinaryOperation", binder, GetArgs(arg), (e) => binder.FallbackBinaryOperation(this, arg, e)); 
                }

                return base.BindBinaryOperation(binder, arg);
            } 

            public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) { 
                if (IsOverridden("TryUnaryOperation")) { 
                    return CallMethodWithResult("TryUnaryOperation", binder, NoArgs, (e) => binder.FallbackUnaryOperation(this, e));
                } 

                return base.BindUnaryOperation(binder);
            }
 
            public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) {
                if (IsOverridden("TryGetIndex")) { 
                    return CallMethodWithResult("TryGetIndex", binder, GetArgArray(indexes), (e) => binder.FallbackGetIndex(this, indexes, e)); 
                }
 
                return base.BindGetIndex(binder, indexes);
            }

            public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { 
                if (IsOverridden("TrySetIndex")) {
                    return CallMethodReturnLast("TrySetIndex", binder, GetArgArray(indexes, value), (e) => binder.FallbackSetIndex(this, indexes, value, e)); 
                } 

                return base.BindSetIndex(binder, indexes, value); 
            }

            public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) {
                if (IsOverridden("TryDeleteIndex")) { 
                    return CallMethodNoResult("TryDeleteIndex", binder, GetArgArray(indexes), (e) => binder.FallbackDeleteIndex(this, indexes, e));
                } 
 
                return base.BindDeleteIndex(binder, indexes);
            } 

            private delegate DynamicMetaObject Fallback(DynamicMetaObject errorSuggestion);

            private readonly static Expression[] NoArgs = new Expression[0]; 

            private static Expression[] GetArgs(params DynamicMetaObject[] args) { 
                Expression[] paramArgs = DynamicMetaObject.GetExpressions(args); 

                for (int i = 0; i < paramArgs.Length; i++) { 
                    paramArgs[i] = Expression.Convert(args[i].Expression, typeof(object));
                }

                return paramArgs; 
            }
 
            private static Expression[] GetArgArray(DynamicMetaObject[] args) { 
                return new[] { Expression.NewArrayInit(typeof(object), GetArgs(args)) };
            } 

            private static Expression[] GetArgArray(DynamicMetaObject[] args, DynamicMetaObject value) {
                return new Expression[] {
                    Expression.NewArrayInit(typeof(object), GetArgs(args)), 
                    Expression.Convert(value.Expression, typeof(object))
                }; 
            } 

            private static ConstantExpression Constant(DynamicMetaObjectBinder binder) { 
                Type t = binder.GetType();
                while (!t.IsVisible) {
                    t = t.BaseType;
                } 
                return Expression.Constant(binder, t);
            } 
 
            /// 
            /// Helper method for generating a MetaObject which calls a 
            /// specific method on Dynamic that returns a result
            /// 
            private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
                return CallMethodWithResult(methodName, binder, args, fallback, null); 
            }
 
            ///  
            /// Helper method for generating a MetaObject which calls a
            /// specific method on Dynamic that returns a result 
            /// 
            private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback, Fallback fallbackInvoke) {
                //
                // First, call fallback to do default binding 
                // This produces either an error or a call to a .NET member
                // 
                DynamicMetaObject fallbackResult = fallback(null); 

                var callDynamic = BuildCallMethodWithResult(methodName, binder, args, fallbackResult, fallbackInvoke); 

                //
                // Now, call fallback again using our new MO as the error
                // When we do this, one of two things can happen: 
                //   1. Binding will succeed, and it will ignore our call to
                //      the dynamic method, OR 
                //   2. Binding will fail, and it will use the MO we created 
                //      above.
                // 
                return fallback(callDynamic);
            }

            private DynamicMetaObject BuildCallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, DynamicMetaObject fallbackResult, Fallback fallbackInvoke) { 
                if (!IsOverridden(methodName)) {
                    return fallbackResult; 
                } 

                // 
                // Build a new expression like:
                // {
                //   object result;
                //   TryGetMember(payload, out result) ? fallbackInvoke(result) : fallbackResult 
                // }
                // 
                var result = Expression.Parameter(typeof(object), null); 

                var callArgs = new Expression[args.Length + 2]; 
                Array.Copy(args, 0, callArgs, 1, args.Length);
                callArgs[0] = Constant(binder);
                callArgs[callArgs.Length - 1] = result;
 
                var resultMO = new DynamicMetaObject(result, BindingRestrictions.Empty);
 
                // Need to add a conversion if calling TryConvert 
                if (binder.ReturnType != typeof(object)) {
                    Debug.Assert(binder is ConvertBinder && fallbackInvoke == null); 

                    var convert = Expression.Convert(resultMO.Expression, binder.ReturnType);
                    // will always be a cast or unbox
                    Debug.Assert(convert.Method == null); 

                    resultMO = new DynamicMetaObject(convert, resultMO.Restrictions); 
                } 

                if (fallbackInvoke != null) { 
                    resultMO = fallbackInvoke(resultMO);
                }

                var callDynamic = new DynamicMetaObject( 
                    Expression.Block(
                        new[] { result }, 
                        Expression.Condition( 
                            Expression.Call(
                                GetLimitedSelf(), 
                                typeof(DynamicObject).GetMethod(methodName),
                                callArgs
                            ),
                            resultMO.Expression, 
                            fallbackResult.Expression,
                            binder.ReturnType 
                        ) 
                    ),
                    GetRestrictions().Merge(resultMO.Restrictions).Merge(fallbackResult.Restrictions) 
                );
                return callDynamic;
            }
 

            ///  
            /// Helper method for generating a MetaObject which calls a 
            /// specific method on Dynamic, but uses one of the arguments for
            /// the result. 
            /// 
            private DynamicMetaObject CallMethodReturnLast(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
                //
                // First, call fallback to do default binding 
                // This produces either an error or a call to a .NET member
                // 
                DynamicMetaObject fallbackResult = fallback(null); 

                // 
                // Build a new expression like:
                // {
                //   object result;
                //   TrySetMember(payload, result = value) ? result : fallbackResult 
                // }
                // 
 
                var result = Expression.Parameter(typeof(object), null);
                var callArgs = args.AddFirst(Constant(binder)); 
                callArgs[args.Length] = Expression.Assign(result, callArgs[args.Length]);

                var callDynamic = new DynamicMetaObject(
                    Expression.Block( 
                        new[] { result },
                        Expression.Condition( 
                            Expression.Call( 
                                GetLimitedSelf(),
                                typeof(DynamicObject).GetMethod(methodName), 
                                callArgs
                            ),
                            result,
                            fallbackResult.Expression, 
                            typeof(object)
                        ) 
                    ), 
                    GetRestrictions().Merge(fallbackResult.Restrictions)
                ); 

                //
                // Now, call fallback again using our new MO as the error
                // When we do this, one of two things can happen: 
                //   1. Binding will succeed, and it will ignore our call to
                //      the dynamic method, OR 
                //   2. Binding will fail, and it will use the MO we created 
                //      above.
                // 
                return fallback(callDynamic);
            }

 
            /// 
            /// Helper method for generating a MetaObject which calls a 
            /// specific method on Dynamic, but uses one of the arguments for 
            /// the result.
            ///  
            private DynamicMetaObject CallMethodNoResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
                //
                // First, call fallback to do default binding
                // This produces either an error or a call to a .NET member 
                //
                DynamicMetaObject fallbackResult = fallback(null); 
 
                //
                // Build a new expression like: 
                //   if (TryDeleteMember(payload)) { } else { fallbackResult }
                //
                var callDynamic = new DynamicMetaObject(
                    Expression.Condition( 
                        Expression.Call(
                            GetLimitedSelf(), 
                            typeof(DynamicObject).GetMethod(methodName), 
                            args.AddFirst(Constant(binder))
                        ), 
                        Expression.Empty(),
                        fallbackResult.Expression,
                        typeof(void)
                    ), 
                    GetRestrictions().Merge(fallbackResult.Restrictions)
                ); 
 
                //
                // Now, call fallback again using our new MO as the error 
                // When we do this, one of two things can happen:
                //   1. Binding will succeed, and it will ignore our call to
                //      the dynamic method, OR
                //   2. Binding will fail, and it will use the MO we created 
                //      above.
                // 
                return fallback(callDynamic); 
            }
 
            /// 
            /// Checks if the derived type has overridden the specified method.  If there is no
            /// implementation for the method provided then Dynamic falls back to the base class
            /// behavior which lets the call site determine how the binder is performed. 
            /// 
            private bool IsOverridden(string method) { 
                var methods = Value.GetType().GetMember(method, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance); 

                foreach (MethodInfo mi in methods) { 
                    if (mi.DeclaringType != typeof(DynamicObject) && mi.GetBaseDefinition().DeclaringType == typeof(DynamicObject)) {
                        return true;
                    }
                } 

                return false; 
            } 

            ///  
            /// Returns a Restrictions object which includes our current restrictions merged
            /// with a restriction limiting our type
            /// 
            private BindingRestrictions GetRestrictions() { 
                Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty");
 
                return BindingRestrictions.GetTypeRestriction(this); 
            }
 
            /// 
            /// Returns our Expression converted to DynamicObject
            /// 
            private Expression GetLimitedSelf() { 
                // Convert to DynamicObject rather than LimitType, because
                // the limit type might be non-public. 
                if (TypeUtils.AreEquivalent(Expression.Type, typeof(DynamicObject))) { 
                    return Expression;
                } 
                return Expression.Convert(Expression, typeof(DynamicObject));
            }

            private new DynamicObject Value { 
                get {
                    return (DynamicObject)base.Value; 
                } 
            }
 
            // It is okay to throw NotSupported from this binder. This object
            // is only used by DynamicObject.GetMember--it is not expected to
            // (and cannot) implement binding semantics. It is just so the DO
            // can use the Name and IgnoreCase properties. 
            private sealed class GetBinderAdapter : GetMemberBinder {
                internal GetBinderAdapter(InvokeMemberBinder binder) 
                    : base(binder.Name, binder.IgnoreCase) { 
                }
 
                public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) {
                    throw new NotSupportedException();
                }
            } 
        }
 
        #endregion 

        #region IDynamicMetaObjectProvider Members 

        /// 
        /// The provided MetaObject will dispatch to the Dynamic virtual methods.
        /// The object can be encapsulated inside of another MetaObject to 
        /// provide custom behavior for individual actions.
        ///  
        public virtual DynamicMetaObject GetMetaObject(Expression parameter) { 
            return new MetaDynamic(parameter, this);
        } 

        #endregion
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.