File: Microsoft\CSharp\RuntimeBinder\DynamicDebuggerProxy.cs
Web Access
Project: src\src\libraries\Microsoft.CSharp\src\Microsoft.CSharp.csproj (Microsoft.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
 
namespace Microsoft.CSharp.RuntimeBinder
{
    [Serializable]
    [EditorBrowsable(EditorBrowsableState.Never)]
    internal sealed class DynamicBindingFailedException : Exception
    {
        public DynamicBindingFailedException()
            : base()
        {
        }
 
        [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        private DynamicBindingFailedException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }
 
    internal sealed class GetMemberValueBinder : GetMemberBinder
    {
        public GetMemberValueBinder(string name, bool ignoreCase)
            : base(name, ignoreCase)
        {
        }
 
        public override DynamicMetaObject FallbackGetMember(DynamicMetaObject self, DynamicMetaObject onBindingError)
        {
            if (onBindingError == null)
            {
                var v = new List<DynamicMetaObject> { self };
                var error = new DynamicMetaObject(System.Linq.Expressions.Expression.Throw(
                    System.Linq.Expressions.Expression.Constant(new DynamicBindingFailedException(), typeof(Exception)), typeof(object)), System.Dynamic.BindingRestrictions.Combine(v));
                return error;
            }
            return onBindingError;
        }
    }
 
    internal sealed class DynamicMetaObjectProviderDebugView
    {
        [System.Diagnostics.DebuggerDisplay("{value}", Name = "{name, nq}", Type = "{type, nq}")]
        internal sealed class DynamicProperty
        {
            [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
            private readonly string name;
 
            [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
            private readonly object value;
 
            [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
            private readonly string type;
 
            public DynamicProperty(string name, object value)
            {
                this.name = name;
                this.value = value;
                this.type = value == null ? "<null>" : value.GetType().ToString();
            }
        }
 
        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
        private IList<KeyValuePair<string, object>> results = null;
 
        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
        private readonly object obj;
 
        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
        internal DynamicProperty[] Items
        {
            [RequiresUnreferencedCode(Binder.TrimmerWarning)]
            get
            {
                if (results == null || results.Count == 0)
                {
                    results = QueryDynamicObject(obj);
                    if (results == null || results.Count == 0)
                    {
                        throw new DynamicDebugViewEmptyException();
                    }
                }
                DynamicProperty[] pairArray = new DynamicProperty[results.Count];
                for (int i = 0; i < results.Count; i++)
                {
                    pairArray[i] = new DynamicProperty(results[i].Key, results[i].Value);
                }
                return pairArray;
            }
        }
 
        public DynamicMetaObjectProviderDebugView(object arg)
        {
            this.obj = arg;
        }
 
        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
        private static readonly ParameterExpression parameter = Expression.Parameter(typeof(object), "debug");
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static object TryEvalBinaryOperators<T1, T2>(
            T1 arg1,
            T2 arg2,
            CSharpArgumentInfoFlags arg1Flags,
            CSharpArgumentInfoFlags arg2Flags,
            ExpressionType opKind,
            Type accessibilityContext)
        {
            CSharpArgumentInfo arg1Info = CSharpArgumentInfo.Create(arg1Flags, null);
            CSharpArgumentInfo arg2Info = CSharpArgumentInfo.Create(arg2Flags, null);
 
            CSharpBinaryOperationBinder binder = new CSharpBinaryOperationBinder(
                opKind,
                false, // isChecked
                CSharpBinaryOperationFlags.None,
                accessibilityContext,
                new CSharpArgumentInfo[] { arg1Info, arg2Info });
 
            var site = CallSite<Func<CallSite, T1, T2, object>>.Create(binder);
            return site.Target(site, arg1, arg2);
        }
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static object TryEvalUnaryOperators<T>(T obj, ExpressionType oper, Type accessibilityContext)
        {
            if (oper == ExpressionType.IsTrue || oper == ExpressionType.IsFalse)
            {
                var trueFalseSite = CallSite<Func<CallSite, T, bool>>
                    .Create(new Microsoft.CSharp.RuntimeBinder.CSharpUnaryOperationBinder(oper,
                        false,
                        accessibilityContext,
                        new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
                return trueFalseSite.Target(trueFalseSite, obj);
            }
 
            var site = CallSite<Func<CallSite, T, object>>
                .Create(new Microsoft.CSharp.RuntimeBinder.CSharpUnaryOperationBinder(oper,
                    false,
                    accessibilityContext,
                    new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
            return site.Target(site, obj);
        }
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static K TryEvalCast<T, K>(T obj, Type type, CSharpBinderFlags kind, Type accessibilityContext)
        {
            var site = CallSite<Func<CallSite, T, K>>.Create(Binder.Convert(kind, type, accessibilityContext));
            return site.Target(site, obj);
        }
 
        /// <summary>
        /// Creates array of types that describes delegate's signature and array of
        /// CSharpArgumentInfoFlags that describe each of the arguments.
        /// </summary>
        private static void CreateDelegateSignatureAndArgumentInfos(
            object[] args,
            Type[] argTypes,
            CSharpArgumentInfoFlags[] argFlags,
            out Type[] delegateSignatureTypes,
            out CSharpArgumentInfo[] argInfos)
        {
            int numberOfArguments = args.Length;
            Debug.Assert((numberOfArguments == argTypes.Length) && (numberOfArguments == argFlags.Length), "Argument arrays size mismatch.");
 
            delegateSignatureTypes = new Type[numberOfArguments + 2];
            delegateSignatureTypes[0] = typeof(CallSite);
 
            argInfos = new CSharpArgumentInfo[numberOfArguments];
 
            for (int i = 0; i < numberOfArguments; i++)
            {
                if (argTypes[i] != null)
                {
                    delegateSignatureTypes[i + 1] = argTypes[i];
                }
                else if (args[i] != null)
                {
                    delegateSignatureTypes[i + 1] = args[i].GetType();
                }
                else
                {
                    delegateSignatureTypes[i + 1] = typeof(object);
                }
 
                argInfos[i] = CSharpArgumentInfo.Create(argFlags[i], null);
            }
 
            delegateSignatureTypes[numberOfArguments + 1] = typeof(object); // type of return value
        }
 
        /// <summary>
        /// Creates a delegate based on type array that describe its signature and invokes it.
        /// </summary>
        /// <returns>Result of invoking the delegate.</returns>
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        private static object CreateDelegateAndInvoke(Type[] delegateSignatureTypes, CallSiteBinder binder, object[] args)
        {
            Type delegateType = Expression.GetDelegateType(delegateSignatureTypes);
            var site = CallSite.Create(delegateType, binder);
 
            Delegate target = (Delegate)site.GetType().GetField("Target").GetValue(site);
 
            object[] argsWithSite = new object[args.Length + 1];
            argsWithSite[0] = site;
            args.CopyTo(argsWithSite, 1);
 
            object result = target.DynamicInvoke(argsWithSite);
            return result;
        }
 
        /// <summary>
        /// DynamicOperatorRewriter in EE generates call to this method to dynamically invoke a method.
        /// </summary>
        /// <param name="methodArgs">Array that contains method arguments. The first element is an object on which method should be called.</param>
        /// <param name="argTypes">Type of each argument in methodArgs.</param>
        /// <param name="argFlags">Flags describing each argument.</param>
        /// <param name="methodName">Name of a method to invoke.</param>
        /// <param name="accessibilityContext">Type that determines context in which method should be called.</param>
        /// <param name="typeArguments">Generic type arguments if there are any.</param>
        /// <returns>Result of method invocation.</returns>
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static object TryEvalMethodVarArgs(
            object[] methodArgs,
            Type[] argTypes,
            CSharpArgumentInfoFlags[] argFlags,
            string methodName,
            Type accessibilityContext,
            Type[] typeArguments)
        {
            Type[] delegateSignatureTypes;
            CSharpArgumentInfo[] argInfos;
 
            CreateDelegateSignatureAndArgumentInfos(
                methodArgs,
                argTypes,
                argFlags,
                out delegateSignatureTypes,
                out argInfos);
 
            CallSiteBinder binder;
            if (string.IsNullOrEmpty(methodName))
            {
                //null or empty indicates delegate invocation.
                binder = new CSharpInvokeBinder(
                    CSharpCallFlags.ResultDiscarded,
                    accessibilityContext,
                    argInfos);
            }
            else
            {
                binder = new CSharpInvokeMemberBinder(
                    CSharpCallFlags.ResultDiscarded,
                    methodName,
                    accessibilityContext,
                    typeArguments,
                    argInfos);
            }
 
            return CreateDelegateAndInvoke(delegateSignatureTypes, binder, methodArgs);
        }
 
        /// <summary>
        /// DynamicOperatorRewriter in EE generates call to this method to dynamically invoke a property getter
        /// with no arguments.
        /// </summary>
        /// <typeparam name="T">Type of object on which property is defined.</typeparam>
        /// <param name="obj">Object on which property is defined.</param>
        /// <param name="propName">Name of a property to invoke.</param>
        /// <param name="accessibilityContext">Type that determines context in which method should be called.</param>
        /// <param name="isResultIndexed">Determines if COM binder should return a callable object.</param>
        /// <returns>Result of property invocation.</returns>
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static object TryGetMemberValue<T>(T obj, string propName, Type accessibilityContext, bool isResultIndexed)
        {
            // In most cases it's ok to use CSharpArgumentInfoFlags.None since target of property call is dynamic.
            // The only possible case when target is not dynamic but we still treat is as dynamic access is when
            // one of arguments is dynamic. This is only possible for indexed properties since we call this method and
            // TryGetMemberValueVarArgs afterwards.
 
            CSharpGetMemberBinder binder = new CSharpGetMemberBinder(
                propName,
                isResultIndexed,
                accessibilityContext,
                new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
 
            var site = CallSite<Func<CallSite, T, object>>.Create(binder);
            return site.Target(site, obj);
        }
 
        /// <summary>
        /// DynamicOperatorRewriter in EE generates call to this method to dynamically invoke a property/indexer getter.
        /// </summary>
        /// <param name="propArgs">Array that contains property arguments. The first element is an object on
        /// which indexer should be called or call to TryGetMemberValue that selects the right property in case of
        /// indexed properties.</param>
        /// <param name="argTypes">Type of each argument in propArgs.</param>
        /// <param name="argFlags">Flags describing each argument.</param>
        /// <param name="accessibilityContext">Type that determines context in which method should be called.</param>
        /// <returns>Result of property invocation.</returns>
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static object TryGetMemberValueVarArgs(
            object[] propArgs,
            Type[] argTypes,
            CSharpArgumentInfoFlags[] argFlags,
            Type accessibilityContext)
        {
            Type[] delegateSignatureTypes;
            CSharpArgumentInfo[] argInfos;
 
            CreateDelegateSignatureAndArgumentInfos(
                propArgs,
                argTypes,
                argFlags,
                out delegateSignatureTypes,
                out argInfos);
 
            CallSiteBinder binder = new CSharpGetIndexBinder(accessibilityContext, argInfos);
 
            return CreateDelegateAndInvoke(delegateSignatureTypes, binder, propArgs);
        }
 
        /// <summary>
        /// DynamicOperatorRewriter in EE generates call to this method to dynamically invoke a property setter
        /// with no arguments.
        /// </summary>
        /// <typeparam name="TObject">Type of object on which property is defined.</typeparam>
        /// <typeparam name="TValue">Type of value property needs to be set to.</typeparam>
        /// <param name="obj">Object on which property is defined.</param>
        /// <param name="propName">Name of a property to invoke.</param>
        /// <param name="value">Value property needs to be set to.</param>
        /// <param name="valueFlags"></param>
        /// <param name="accessibilityContext">Type that determines context in which method should be called.</param>
        /// <returns>Result of property invocation.</returns>
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static object TrySetMemberValue<TObject, TValue>(
            TObject obj,
            string propName,
            TValue value,
            CSharpArgumentInfoFlags valueFlags,
            Type accessibilityContext)
        {
            CSharpArgumentInfo targetArgInfo = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
            CSharpArgumentInfo valueArgInfo = CSharpArgumentInfo.Create(valueFlags, null);
 
            CSharpSetMemberBinder binder = new CSharpSetMemberBinder(
                propName,
                false, // isCompoundAssignment
                false, // isChecked
                accessibilityContext,
                new CSharpArgumentInfo[] { targetArgInfo, valueArgInfo });
 
            var site = CallSite<Func<CallSite, TObject, TValue, object>>.Create(binder);
            return site.Target(site, obj, value);
        }
 
        /// <summary>
        /// DynamicOperatorRewriter in EE generates call to this method to dynamically invoke a property/indexer setter.
        /// </summary>
        /// <param name="propArgs">Array that contains property arguments. The first element is an object on
        /// which indexer should be called or call to TrySetMemberValue that selects the right property in case of
        /// indexed properties. The last argument is value that property should be set to.</param>
        /// <param name="argTypes">Type of each argument in propArgs.</param>
        /// <param name="argFlags">Flags describing each argument.</param>
        /// <param name="accessibilityContext">Type that determines context in which method should be called.</param>
        /// <returns>Result of property invocation.</returns>
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static object TrySetMemberValueVarArgs(
            object[] propArgs,
            Type[] argTypes,
            CSharpArgumentInfoFlags[] argFlags,
            Type accessibilityContext)
        {
            Type[] delegateSignatureTypes;
            CSharpArgumentInfo[] argInfos;
 
            CreateDelegateSignatureAndArgumentInfos(
                propArgs,
                argTypes,
                argFlags,
                out delegateSignatureTypes,
                out argInfos);
 
            CallSiteBinder binder = new CSharpSetIndexBinder(/*isCompoundAssignment */ false, /* isChecked */ false, accessibilityContext, argInfos);
 
            return CreateDelegateAndInvoke(delegateSignatureTypes, binder, propArgs);
        }
 
        //Called when we don't know if the member is a property or a method
        internal static object TryGetMemberValue(object obj, string name, bool ignoreException)
        {
            // if you want to ignore case for VB, this is how you set it .. make it a member and add a ctor to init it
            bool ignoreCase = false;
            object value = null;
 
            var site = CallSite<Func<CallSite, object, object>>.Create(new GetMemberValueBinder(name, ignoreCase));
 
            try
            {
                value = site.Target(site, obj);
            }
            catch (DynamicBindingFailedException)
            {
                if (ignoreException)
                    value = null;
                else
                    throw;
            }
            catch (MissingMemberException)
            {
                if (ignoreException)
                    value = SR.GetValueonWriteOnlyProperty;
                else
                    throw;
            }
            return value;
        }
 
#if ENABLECOMBINDER
        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
        private static readonly Type ComObjectType = Type.GetType("System.__ComObject, System.Private.CoreLib");
#endif
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        private static IList<KeyValuePair<string, object>> QueryDynamicObject(object obj)
        {
            IDynamicMetaObjectProvider ido = obj as IDynamicMetaObjectProvider;
            if (ido != null)
            {
                DynamicMetaObject mo = ido.GetMetaObject(parameter);
                List<string> names = new List<string>(mo.GetDynamicMemberNames());
                names.Sort();
 
                var result = new List<KeyValuePair<string, object>>();
                foreach (string name in names)
                {
                    object value;
                    if ((value = TryGetMemberValue(obj, name, true)) != null)
                    {
                        result.Add(new KeyValuePair<string, object>(name, value));
                    }
                }
 
                return result;
            }
#if ENABLECOMBINDER
            else if (obj != null && ComObjectType.IsAssignableFrom(obj.GetType()))
            {
                IList<string> names = ComInterop.ComBinder.GetDynamicDataMemberNames(obj);
                return ComInterop.ComBinder.GetDynamicDataMembers(obj, names.OrderBy(n => n));
            }
#endif
            return Array.Empty<KeyValuePair<string, object>>();
        }
 
        [Serializable]
        internal sealed class DynamicDebugViewEmptyException : Exception
        {
            public DynamicDebugViewEmptyException()
            {
            }
 
            [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
            private DynamicDebugViewEmptyException(SerializationInfo info, StreamingContext context)
                : base(info, context)
            {
            }
 
#pragma warning disable CA1822
            // This property value is used by the debugger EE as the message
            // displayed when a dynamic object has no members.
            public string Empty
            {
                get
                {
                    return SR.EmptyDynamicView;
                }
            }
#pragma warning restore CA1822
        }
    }
}