File: System\Reflection\Context\Custom\CustomType.cs
Web Access
Project: src\src\libraries\System.Reflection.Context\src\System.Reflection.Context.csproj (System.Reflection.Context)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Context.Projection;
using System.Reflection.Context.Virtual;
 
namespace System.Reflection.Context.Custom
{
    internal sealed class CustomType : ProjectingType
    {
        private IEnumerable<PropertyInfo>? _newProperties;
 
        public CustomType(Type template, CustomReflectionContext context)
            : base(template, context.Projector)
        {
            ReflectionContext = context;
        }
 
        public CustomReflectionContext ReflectionContext { get; }
 
        // Currently only the results of GetCustomAttributes can be customizaed.
        // We don't need to override GetCustomAttributesData.
        public override object[] GetCustomAttributes(bool inherit)
        {
            return GetCustomAttributes(typeof(object), inherit);
        }
 
        public override object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            return AttributeUtils.GetCustomAttributes(ReflectionContext, this, attributeType, inherit);
        }
 
        public override bool IsDefined(Type attributeType, bool inherit)
        {
            return AttributeUtils.IsDefined(this, attributeType, inherit);
        }
 
        public override bool IsInstanceOfType([NotNullWhen(true)] object? o)
        {
            Type objectType = ReflectionContext.GetTypeForObject(o!);
            return IsAssignableFrom(objectType);
        }
 
        public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
        {
            // list of properties on this type according to the underlying ReflectionContext
            PropertyInfo[] properties = base.GetProperties(bindingAttr);
 
            // Optimization: we currently don't support adding nonpublic or static properties,
            // so if Public or Instance is not set we don't need to check for new properties.
            bool getDeclaredOnly = (bindingAttr & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly;
            bool getInstance = (bindingAttr & BindingFlags.Instance) == BindingFlags.Instance;
            bool getPublic = (bindingAttr & BindingFlags.Public) == BindingFlags.Public;
            if (!getPublic || !getInstance)
                return properties;
 
            List<PropertyInfo> results = new List<PropertyInfo>(properties);
 
            //Unlike in runtime reflection, the newly added properties don't hide properties with the same name and signature.
            results.AddRange(NewProperties);
 
            // adding new properties declared on base types
            if (!getDeclaredOnly)
            {
                CustomType? baseType = BaseType as CustomType;
                while (baseType != null)
                {
                    IEnumerable<PropertyInfo> newProperties = baseType.NewProperties;
 
                    // We shouldn't add a base type property directly on a subtype.
                    // A new property with a different ReflectedType should be used.
                    foreach (PropertyInfo prop in newProperties)
                        results.Add(new InheritedPropertyInfo(prop, this));
 
                    baseType = baseType.BaseType as CustomType;
                }
            }
 
            return results.ToArray();
        }
 
        protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, Type? returnType, Type[]? types, ParameterModifier[]? modifiers)
        {
            PropertyInfo? property = base.GetPropertyImpl(name, bindingAttr, binder, returnType, types, modifiers);
 
            bool getIgnoreCase = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase;
            bool getDeclaredOnly = (bindingAttr & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly;
            bool getInstance = (bindingAttr & BindingFlags.Instance) == BindingFlags.Instance;
            bool getPublic = (bindingAttr & BindingFlags.Public) == BindingFlags.Public;
 
            // If the ReflectionContext adds a property with identical name and type to an existing property,
            // the behavior is unspecified.
            // In this implementation, we return the existing property.
            if (!getPublic || !getInstance)
                return property;
 
            // Adding indexer properties is currently not supported.
            if (types != null && types.Length > 0)
                return property;
 
            List<PropertyInfo> matchingProperties = new List<PropertyInfo>();
            if (property != null)
                matchingProperties.Add(property);
 
            // If the ReflectionContext adds two or more properties with the same name and type,
            // the behavior is unspecified.
            // In this implementation, we throw AmbiguousMatchException even if the two properties are
            // defined on different types (base and sub classes).
 
            StringComparison comparison = getIgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
 
            CustomType? type = this;
            foreach (PropertyInfo newDeclaredProperty in type.NewProperties)
            {
                if (string.Equals(newDeclaredProperty.Name, name, comparison))
                    matchingProperties.Add(newDeclaredProperty);
            }
 
            if (!getDeclaredOnly)
            {
                while ((type = type.BaseType as CustomType) != null)
                {
                    foreach (PropertyInfo newBaseProperty in type.NewProperties)
                    {
                        if (string.Equals(newBaseProperty.Name, name, comparison))
                            matchingProperties.Add(new InheritedPropertyInfo(newBaseProperty, this));
                    }
                }
            }
 
            if (matchingProperties.Count == 0)
                return null;
 
            binder ??= Type.DefaultBinder;
 
            return binder.SelectProperty(bindingAttr, matchingProperties.ToArray(), returnType, types, modifiers);
        }
 
        public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
        {
            // list of methods on this type according to the underlying ReflectionContext
            MethodInfo[] methods = base.GetMethods(bindingAttr);
 
            // Optimization: we currently don't support adding nonpublic or static property getters or setters,
            // so if Public or Instance is not set we don't need to check for new properties.
            bool getDeclaredOnly = (bindingAttr & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly;
            bool getInstance = (bindingAttr & BindingFlags.Instance) == BindingFlags.Instance;
            bool getPublic = (bindingAttr & BindingFlags.Public) == BindingFlags.Public;
            if (!getPublic || !getInstance)
                return methods;
 
            List<MethodInfo> results = new List<MethodInfo>(methods);
 
            // in runtime reflection hidden methods are always returned in GetMethods
            foreach (PropertyInfo prop in NewProperties)
            {
                results.AddRange(prop.GetAccessors());
            }
 
            // adding new methods declared on base types
            if (!getDeclaredOnly)
            {
                CustomType? baseType = BaseType as CustomType;
                while (baseType != null)
                {
                    // We shouldn't add a base type method directly on a subtype.
                    // A new method with a different ReflectedType should be used.
                    foreach (PropertyInfo prop in baseType.NewProperties)
                    {
                        PropertyInfo inheritedProperty = new InheritedPropertyInfo(prop, this);
                        results.AddRange(inheritedProperty.GetAccessors());
                    }
 
                    baseType = baseType.BaseType as CustomType;
                }
            }
 
            return results.ToArray();
        }
 
        protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers)
        {
            MethodInfo? method = base.GetMethodImpl(name, bindingAttr, binder, callConvention, types, modifiers);
 
            bool getIgnoreCase = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase;
            bool getDeclaredOnly = (bindingAttr & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly;
            bool getInstance = (bindingAttr & BindingFlags.Instance) == BindingFlags.Instance;
            bool getPublic = (bindingAttr & BindingFlags.Public) == BindingFlags.Public;
 
            // If the ReflectionContext adds a property with identical name and type to an existing property,
            // the behavior is unspecified.
            // In this implementation, we return the existing method.
            if (!getPublic || !getInstance)
                return method;
 
            bool getPropertyGetter = false;
            bool getPropertySetter = false;
 
            StringComparison comparison = getIgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
 
            if (name.Length > 4)
            {
                // Right now we don't support adding fabricated indexers on types
                getPropertyGetter = (types == null || types.Length == 0) && name.StartsWith("get_", comparison);
 
                if (!getPropertyGetter)
                    getPropertySetter = (types == null || types.Length == 1) && name.StartsWith("set_", comparison);
            }
 
            // not a property getter or setter
            if (!getPropertyGetter && !getPropertySetter)
                return method;
 
            // get the target property name by removing "get_" or "set_"
            string targetPropertyName = name.Substring(4);
 
            List<MethodInfo> matchingMethods = new List<MethodInfo>();
            if (method != null)
                matchingMethods.Add(method);
 
            // in runtime reflection hidden methods are always returned in GetMethods
            foreach (PropertyInfo newDeclaredProperty in NewProperties)
            {
                if (string.Equals(newDeclaredProperty.Name, targetPropertyName, comparison))
                {
                    MethodInfo? accessor = getPropertyGetter ? newDeclaredProperty.GetGetMethod() : newDeclaredProperty.GetSetMethod();
                    if (accessor != null)
                        matchingMethods.Add(accessor);
                }
            }
 
            // adding new methods declared on base types
            if (!getDeclaredOnly)
            {
                CustomType? baseType = BaseType as CustomType;
 
                while (baseType != null)
                {
                    // We shouldn't add a base type method directly on a subtype.
                    // A new method with a different ReflectedType should be used.
                    foreach (PropertyInfo newBaseProperty in baseType.NewProperties)
                    {
                        if (string.Equals(newBaseProperty.Name, targetPropertyName, comparison))
                        {
                            PropertyInfo inheritedProperty = new InheritedPropertyInfo(newBaseProperty, this);
 
                            MethodInfo? accessor = getPropertyGetter ? inheritedProperty.GetGetMethod() : inheritedProperty.GetSetMethod();
                            if (accessor != null)
                                matchingMethods.Add(accessor);
                        }
                    }
 
                    baseType = baseType.BaseType as CustomType;
                }
            }
 
            if (matchingMethods.Count == 0)
                return null;
 
            if (types == null || getPropertyGetter)
            {
                Debug.Assert(types == null || types.Length == 0);
 
                // matches any signature
                MethodInfo match = matchingMethods[0];
                if (matchingMethods.Count == 1)
                    return match;
 
                Type? declaringType = match.DeclaringType;
                throw new AmbiguousMatchException(SR.Format(SR.Arg_AmbiguousMatchException_MemberInfo, declaringType, match));
            }
            else
            {
                Debug.Assert(getPropertySetter && types != null && types.Length == 1);
 
                binder ??= Type.DefaultBinder;
 
                return (MethodInfo?)binder.SelectMethod(bindingAttr, matchingMethods.ToArray(), types, modifiers);
            }
        }
 
        private IEnumerable<PropertyInfo> NewProperties => _newProperties ??= ReflectionContext.GetNewPropertiesForType(this);
    }
}