File: System\Reflection\Context\CustomReflectionContext.cs
Web Access
Project: src\runtime\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.Reflection.Context.Custom;
using System.Reflection.Context.Projection;
using System.Reflection.Context.Virtual;

namespace System.Reflection.Context
{
    internal sealed class IdentityReflectionContext : ReflectionContext
    {
        public override Assembly MapAssembly(Assembly assembly) { return assembly; }
        public override TypeInfo MapType(TypeInfo type) { return type; }
    }

    /// <summary>
    /// Represents a customizable reflection context.
    /// </summary>
    /// <remarks><format type="text/markdown"><![CDATA[
    ///
    /// <xref:System.Reflection.Context.CustomReflectionContext> provides a way for you to add or remove custom attributes from reflection objects, or add dummy
    /// properties to those objects, without re-implementing the complete reflection model. The default <xref:System.Reflection.Context.CustomReflectionContext>
    /// simply wraps reflection objects without making any changes, but by subclassing and overriding the relevant methods, you can add, remove, or change the attributes
    /// that apply to any reflected parameter or member, or add new properties to a reflected type.
    ///
    /// For example, suppose that your code follows the convention of applying a particular attribute to factory methods, but you're now required to work with third-party
    /// code that lacks attributes. You can use <xref:System.Reflection.Context.CustomReflectionContext> to specify a rule for identifying the objects that should have attributes
    /// and to supply the objects with those attributes when they're viewed from your code.
    ///
    /// To use <xref:System.Reflection.Context.CustomReflectionContext> effectively, the code that uses the reflected objects must support the notion of specifying
    /// a reflection context, instead of assuming that all reflected objects are associated with the runtime reflection context. Many reflection methods in .NET provide
    /// a <xref:System.Reflection.ReflectionContext> parameter for this purpose.
    ///
    /// To modify the attributes that are applied to a reflected parameter or member, override the
    /// <xref:System.Reflection.Context.CustomReflectionContext.GetCustomAttributes(System.Reflection.ParameterInfo,System.Collections.Generic.IEnumerable{System.Object})>
    /// or <xref:System.Reflection.Context.CustomReflectionContext.GetCustomAttributes(System.Reflection.MemberInfo,System.Collections.Generic.IEnumerable{System.Object})> method.
    /// These methods take the reflected object and the list of attributes under its current reflection context, and return the list of attributes it should have under
    /// the custom reflection context.
    ///
    /// > [!WARNING]
    /// > <xref:System.Reflection.Context.CustomReflectionContext> overrides should not query attributes on the provided <xref:System.Reflection.MemberInfo> or <xref:System.Reflection.ParameterInfo>
    /// > by calling <xref:System.Reflection.CustomAttributeExtensions.GetCustomAttributes*>; use the `declaredAttributes` sequence passed to the <xref:System.Reflection.Context.CustomReflectionContext.GetCustomAttributes*> overloads instead.
    ///
    /// To add properties to a reflected type, override the <xref:System.Reflection.Context.CustomReflectionContext.AddProperties*> method. The method accepts a parameter that
    /// specifies the reflected type, and returns a list of additional properties. You should use the <xref:System.Reflection.Context.CustomReflectionContext.CreateProperty*>
    /// method to create property objects to return. You can specify delegates when creating the property that serve as the property accessor, and you can omit one of
    /// the accessors to create a read-only or write-only property. Note that such dummy properties have no metadata or Common Intermediate Language (CIL) backing.
    ///
    /// > [!WARNING]
    /// > - Be cautious about equality among reflected objects when you work with reflection contexts, because objects might represent the same reflected object in multiple
    /// >   contexts. You can use the <xref:System.Reflection.Context.CustomReflectionContext.MapType*> method to obtain a particular reflection context's version of a reflected object.
    /// > - A <xref:System.Reflection.Context.CustomReflectionContext> object alters the attributes returned by a particular reflection object, such as those obtained by the
    /// >   <xref:System.Reflection.MemberInfo.GetCustomAttributes*> method. It doesn't alter the custom attribute data returned by the <xref:System.Reflection.MemberInfo.GetCustomAttributesData*>
    /// >   method, and these two lists won't match when you use a custom reflection context.
    /// ]]></format></remarks>
    /// <example>
    /// The following example demonstrates how to subclass <see cref="CustomReflectionContext" />
    /// to add a custom attribute to all the members of a given type whose names begin with "To".
    /// <code lang="cs" source="../../../../tests/CustomReflectionContext.Examples.cs" region="Snippet1" />
    /// <code lang="cs" source="../../../../tests/CustomReflectionContext.Examples.cs" region="Snippet2" />
    /// </example>
    public abstract partial class CustomReflectionContext : ReflectionContext
    {
        private readonly ReflectionContextProjector _projector;

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomReflectionContext"/> class.
        /// </summary>
        protected CustomReflectionContext() : this(new IdentityReflectionContext()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomReflectionContext"/> class with the specified reflection context as a base.
        /// </summary>
        /// <param name="source">The reflection context to use as a base.</param>
        protected CustomReflectionContext(ReflectionContext source)
        {
            ArgumentNullException.ThrowIfNull(source);

            SourceContext = source;
            _projector = new ReflectionContextProjector(this);
        }

        /// <summary>
        /// Gets the representation, in this reflection context, of an assembly that is represented by an object from another reflection context.
        /// </summary>
        /// <param name="assembly">The external representation of the assembly to represent in this context.</param>
        /// <returns>The representation of the assembly in this reflection context.</returns>
        public override Assembly MapAssembly(Assembly assembly)
        {
            ArgumentNullException.ThrowIfNull(assembly);

            return _projector.ProjectAssemblyIfNeeded(assembly);
        }

        /// <summary>
        /// Gets the representation, in this reflection context, of a type represented by an object from another reflection context.
        /// </summary>
        /// <param name="type">The external representation of the type to represent in this context.</param>
        /// <returns>The representation of the type in this reflection context.</returns>
        public override TypeInfo MapType(TypeInfo type)
        {
            ArgumentNullException.ThrowIfNull(type);

            return _projector.ProjectTypeIfNeeded(type);
        }

        /// <summary>
        /// When overridden in a derived class, provides a list of custom attributes for the specified member, as represented in this reflection context.
        /// </summary>
        /// <param name="member">The member whose custom attributes will be returned.</param>
        /// <param name="declaredAttributes">A collection of the member's attributes in its current context.</param>
        /// <returns>A collection that represents the custom attributes of the specified member in this reflection context.</returns>
        protected virtual IEnumerable<object> GetCustomAttributes(MemberInfo member, IEnumerable<object> declaredAttributes)
        {
            return declaredAttributes;
        }

        /// <summary>
        /// When overridden in a derived class, provides a list of custom attributes for the specified parameter, as represented in this reflection context.
        /// </summary>
        /// <param name="parameter">The parameter whose custom attributes will be returned.</param>
        /// <param name="declaredAttributes">A collection of the parameter's attributes in its current context.</param>
        /// <returns>A collection that represents the custom attributes of the specified parameter in this reflection context.</returns>
        protected virtual IEnumerable<object> GetCustomAttributes(ParameterInfo parameter, IEnumerable<object> declaredAttributes)
        {
            return declaredAttributes;
        }

        // The default implementation of GetProperties: just return an empty list.
        /// <summary>
        /// When overridden in a derived class, provides a collection of additional properties for the specified type, as represented in this reflection context.
        /// </summary>
        /// <param name="type">The type to add properties to.</param>
        /// <returns>A collection of additional properties for the specified type.</returns>
        /// <remarks>
        /// Override this method to specify which properties should be added to a given type. To create the properties, use the <see cref="CreateProperty(Type, string, Func{object, object?}?, Action{object, object?}?)"/> method.
        /// </remarks>
        protected virtual IEnumerable<PropertyInfo> AddProperties(Type type)
        {
            // return an empty enumeration
            yield break;
        }

        /// <summary>
        /// Creates an object that represents a property to be added to a type, to be used with the <see cref="AddProperties(Type)"/> method.
        /// </summary>
        /// <param name="propertyType">The type of the property to create.</param>
        /// <param name="name">The name of the property to create.</param>
        /// <param name="getter">A delegate that represents the property's <see langword="get"/> accessor.</param>
        /// <param name="setter">A delegate that represents the property's <see langword="set"/> accessor.</param>
        /// <returns>An object that represents the property.</returns>
        /// <remarks>
        /// Objects that are returned by this method are not complete <see cref="PropertyInfo"/> objects, and should be used only in the context of the <see cref="AddProperties(Type)"/> method.
        /// </remarks>
        protected PropertyInfo CreateProperty(
            Type propertyType,
            string name,
            Func<object, object?>? getter,
            Action<object, object?>? setter)
        {
            return new VirtualPropertyInfo(
                name,
                propertyType,
                getter,
                setter,
                null,
                null,
                null,
                this);
        }

        /// <summary>
        /// Creates an object that represents a property to be added to a type, to be used with the <see cref="AddProperties(Type)"/> method and using the specified custom attributes.
        /// </summary>
        /// <param name="propertyType">The type of the property to create.</param>
        /// <param name="name">The name of the property to create.</param>
        /// <param name="getter">A delegate that represents the property's <see langword="get"/> accessor.</param>
        /// <param name="setter">A delegate that represents the property's <see langword="set"/> accessor.</param>
        /// <param name="propertyCustomAttributes">A collection of custom attributes to apply to the property.</param>
        /// <param name="getterCustomAttributes">A collection of custom attributes to apply to the property's <see langword="get"/> accessor.</param>
        /// <param name="setterCustomAttributes">A collection of custom attributes to apply to the property's <see langword="set"/> accessor.</param>
        /// <returns>An object that represents the property.</returns>
        /// <remarks>
        /// Objects that are returned by this method are not complete <see cref="PropertyInfo"/> objects, and should be used only in the context of the <see cref="AddProperties(Type)"/> method.
        /// </remarks>
        protected PropertyInfo CreateProperty(
            Type propertyType,
            string name,
            Func<object, object?>? getter,
            Action<object, object?>? setter,
            IEnumerable<Attribute>? propertyCustomAttributes,
            IEnumerable<Attribute>? getterCustomAttributes,
            IEnumerable<Attribute>? setterCustomAttributes)
        {
            return new VirtualPropertyInfo(
                name,
                propertyType,
                getter,
                setter,
                propertyCustomAttributes,
                getterCustomAttributes,
                setterCustomAttributes,
                this);
        }

        internal IEnumerable<PropertyInfo> GetNewPropertiesForType(CustomType type)
        {
            // We don't support adding properties on these types.
            if (type.IsInterface || type.IsGenericParameter || type.HasElementType)
                yield break;

            // Passing in the underlying type.
            IEnumerable<PropertyInfo> newProperties = AddProperties(type.UnderlyingType);

            // Setting DeclaringType on the user provided virtual properties.
            foreach (PropertyInfo prop in newProperties)
            {
                if (prop == null)
                    throw new InvalidOperationException(SR.InvalidOperation_AddNullProperty);

                VirtualPropertyBase? vp = prop as VirtualPropertyBase;
                if (vp == null || vp.ReflectionContext != this)
                    throw new InvalidOperationException(SR.InvalidOperation_AddPropertyDifferentContext);

                if (vp.DeclaringType == null)
                    vp.SetDeclaringType(type);
                else if (!vp.DeclaringType.Equals(type))
                    throw new InvalidOperationException(SR.InvalidOperation_AddPropertyDifferentType);

                yield return prop;
            }
        }

        internal IEnumerable<object> GetCustomAttributesOnMember(MemberInfo member, IEnumerable<object> declaredAttributes, Type attributeFilterType)
        {
            IEnumerable<object> attributes = GetCustomAttributes(member, declaredAttributes);
            return AttributeUtils.FilterCustomAttributes(attributes, attributeFilterType);
        }

        internal IEnumerable<object> GetCustomAttributesOnParameter(ParameterInfo parameter, IEnumerable<object> declaredAttributes, Type attributeFilterType)
        {
            IEnumerable<object> attributes = GetCustomAttributes(parameter, declaredAttributes);
            return AttributeUtils.FilterCustomAttributes(attributes, attributeFilterType);
        }

        internal Projector Projector
        {
            get
            {
                return _projector;
            }
        }

        internal ReflectionContext SourceContext { get; }
    }
}