File: Microsoft\CSharp\RuntimeBinder\RuntimeBinderExtensions.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
 
namespace Microsoft.CSharp.RuntimeBinder
{
    internal static class RuntimeBinderExtensions
    {
        public static bool IsNullableType(this Type type)
        {
            return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
        }
 
        // This method is intended as a means to detect when MemberInfos are the same,
        // modulo the fact that they can appear to have different but equivalent local
        // No-PIA types. It is by the symbol table to determine whether
        // or not members have been added to an AggSym or not.
        public static bool IsEquivalentTo(this MemberInfo mi1, MemberInfo mi2)
        {
            if (mi1 == null || mi2 == null)
            {
                return mi1 == null && mi2 == null;
            }
 
            if (mi1.Equals(mi2))
            {
                return true;
            }
 
            if (mi1 is MethodInfo method1)
            {
                if (!(mi2 is MethodInfo method2) || method1.IsGenericMethod != method2.IsGenericMethod)
                {
                    return false;
                }
 
                if (method1.IsGenericMethod)
                {
                    method1 = method1.GetGenericMethodDefinition();
                    method2 = method2.GetGenericMethodDefinition();
 
                    if (method1.GetGenericArguments().Length != method2.GetGenericArguments().Length)
                    {
                        return false; // Methods of different arity are not equivalent.
                    }
                }
 
                return method1 != method2
                    && method1.CallingConvention == method2.CallingConvention
                    && method1.Name == method2.Name
                    && method1.DeclaringType.IsGenericallyEqual(method2.DeclaringType)
                    && method1.ReturnType.IsGenericallyEquivalentTo(method2.ReturnType, method1, method2)
                    && method1.AreParametersEquivalent(method2);
            }
 
            if (mi1 is ConstructorInfo ctor1)
            {
                return mi2 is ConstructorInfo ctor2
                    && ctor1 != ctor2
                    && ctor1.CallingConvention == ctor2.CallingConvention
                    && ctor1.DeclaringType.IsGenericallyEqual(ctor2.DeclaringType)
                    && ctor1.AreParametersEquivalent(ctor2);
            }
 
            return mi1 is PropertyInfo prop1 && mi2 is PropertyInfo prop2
                && prop1 != prop2
                && prop1.Name == prop2.Name
                && prop1.DeclaringType.IsGenericallyEqual(prop2.DeclaringType)
                && prop1.PropertyType.IsGenericallyEquivalentTo(prop2.PropertyType, prop1, prop2)
                && prop1.GetGetMethod(true).IsEquivalentTo(prop2.GetGetMethod(true))
                && prop1.GetSetMethod(true).IsEquivalentTo(prop2.GetSetMethod(true));
        }
 
        private static bool AreParametersEquivalent(this MethodBase method1, MethodBase method2)
        {
            ParameterInfo[] pis1 = method1.GetParameters();
            ParameterInfo[] pis2 = method2.GetParameters();
            if (pis1.Length != pis2.Length)
            {
                return false;
            }
 
            for (int i = 0; i < pis1.Length; ++i)
            {
                if (!pis1[i].IsEquivalentTo(pis2[i], method1, method2))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        private static bool IsEquivalentTo(this ParameterInfo pi1, ParameterInfo pi2, MethodBase method1, MethodBase method2)
        {
            if (pi1 == null || pi2 == null)
            {
                return pi1 == null && pi2 == null;
            }
 
            if (pi1.Equals(pi2))
            {
                return true;
            }
 
            return pi1.ParameterType.IsGenericallyEquivalentTo(pi2.ParameterType, method1, method2);
        }
 
        private static bool IsGenericallyEqual(this Type t1, Type t2)
        {
            if (t1 == null || t2 == null)
            {
                return t1 == null && t2 == null;
            }
 
            if (t1.Equals(t2))
            {
                return true;
            }
 
            if (t1.IsConstructedGenericType || t2.IsConstructedGenericType)
            {
                Type t1def = t1.IsConstructedGenericType ? t1.GetGenericTypeDefinition() : t1;
                Type t2def = t2.IsConstructedGenericType ? t2.GetGenericTypeDefinition() : t2;
 
                return t1def.Equals(t2def);
            }
 
            return false;
        }
 
        // Compares two types and calls them equivalent if a type parameter equals a type argument.
        // i.e if the inputs are (T, int, C<T>, C<int>) then this will return true.
        private static bool IsGenericallyEquivalentTo(this Type t1, Type t2, MemberInfo member1, MemberInfo member2)
        {
            Debug.Assert(!(member1 is MethodBase) ||
                         !((MethodBase)member1).IsGenericMethod ||
                         (((MethodBase)member1).IsGenericMethodDefinition && ((MethodBase)member2).IsGenericMethodDefinition));
 
            if (t1.Equals(t2))
            {
                return true;
            }
 
            // If one of them is a type param and then the other is a real type, then get the type argument in the member
            // or it's declaring type that corresponds to the type param and compare that to the other type.
            if (t1.IsGenericParameter)
            {
                if (t2.IsGenericParameter)
                {
                    // If member's declaring type is not type parameter's declaring type, we assume that it is used as a type argument
                    if (t1.DeclaringMethod == null && member1.DeclaringType.Equals(t1.DeclaringType))
                    {
                        if (!(t2.DeclaringMethod == null && member2.DeclaringType.Equals(t2.DeclaringType)))
                        {
                            return t1.IsTypeParameterEquivalentToTypeInst(t2, member2);
                        }
                    }
                    else if (t2.DeclaringMethod == null && member2.DeclaringType.Equals(t2.DeclaringType))
                    {
                        return t2.IsTypeParameterEquivalentToTypeInst(t1, member1);
                    }
 
                    // If both of these are type params but didn't compare to be equal then one of them is likely bound to another
                    // open type. Simply disallow such cases.
                    return false;
                }
 
                return t1.IsTypeParameterEquivalentToTypeInst(t2, member2);
            }
            else if (t2.IsGenericParameter)
            {
                return t2.IsTypeParameterEquivalentToTypeInst(t1, member1);
            }
 
            // Recurse in for generic types arrays, byref and pointer types.
            if (t1.IsGenericType && t2.IsGenericType)
            {
                Type[] args1 = t1.GetGenericArguments();
                Type[] args2 = t2.GetGenericArguments();
 
                if (args1.Length == args2.Length)
                {
                    if (!t1.IsGenericallyEqual(t2))
                    {
                        return false;
                    }
 
                    for (int i = 0; i < args1.Length; i++)
                    {
                        if (!args1[i].IsGenericallyEquivalentTo(args2[i], member1, member2))
                        {
                            return false;
                        }
                    }
 
                    return true;
                }
            }
 
            if (t1.IsArray && t2.IsArray)
            {
                return t1.GetArrayRank() == t2.GetArrayRank() &&
                       t1.GetElementType().IsGenericallyEquivalentTo(t2.GetElementType(), member1, member2);
            }
 
            if ((t1.IsByRef && t2.IsByRef) ||
                (t1.IsPointer && t2.IsPointer))
            {
                return t1.GetElementType().IsGenericallyEquivalentTo(t2.GetElementType(), member1, member2);
            }
 
            return false;
        }
 
        private static bool IsTypeParameterEquivalentToTypeInst(this Type typeParam, Type typeInst, MemberInfo member)
        {
            Debug.Assert(typeParam.IsGenericParameter);
 
            if (typeParam.DeclaringMethod != null)
            {
                // The type param is from a generic method. Since only methods can be generic, anything else
                // here means they are not equivalent.
                if (!(member is MethodBase))
                {
                    return false;
                }
 
                MethodBase method = (MethodBase)member;
                int position = typeParam.GenericParameterPosition;
                Type[] args = method.IsGenericMethod ? method.GetGenericArguments() : null;
 
                return args != null &&
                       args.Length > position &&
                       args[position].Equals(typeInst);
            }
            else
            {
                return member.DeclaringType.GetGenericArguments()[typeParam.GenericParameterPosition].Equals(typeInst);
            }
        }
 
        // s_MemberEquivalence will replace itself with one version or another
        // depending on what works at run time
        private static Func<MemberInfo, MemberInfo, bool> s_MemberEquivalence = (m1, m2) =>
            {
                try
                {
                    Type memberInfo = typeof(MemberInfo);
 
                    // First, try the actual API. (Post .NetCore 2.0) The api is the only one that gets it completely right on frameworks without MetadataToken.
                    MethodInfo apiMethod = memberInfo.GetMethod(
                        "HasSameMetadataDefinitionAs",
                        BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding,
                        binder: null,
                        types: new Type[] { typeof(MemberInfo) },
                        modifiers: null);
                    if (apiMethod != null)
                    {
                        Func<MemberInfo, MemberInfo, bool> apiDelegate = apiMethod.CreateDelegate<Func<MemberInfo, MemberInfo, bool>>();
                        try
                        {
                            bool result = apiDelegate(m1, m2);
                            // it worked, so publish it
                            s_MemberEquivalence = apiDelegate;
                            return result;
                        }
                        catch
                        {
                            // Api found but apparently stubbed as not supported. Continue on to the next fallback...
                        }
                    }
 
                    // See if MetadataToken property is available.
                    PropertyInfo property = memberInfo.GetProperty("MetadataToken", typeof(int), Type.EmptyTypes);
 
                    if (property is not null && property.CanRead)
                    {
                        // (parameter1, parameter2) => parameter1.MetadataToken == parameter2.MetadataToken
                        var parameter1 = Expression.Parameter(memberInfo);
                        var parameter2 = Expression.Parameter(memberInfo);
                        var memberEquivalence = Expression.Lambda<Func<MemberInfo, MemberInfo, bool>>(
                            Expression.Equal(
                                Expression.Property(parameter1, property),
                                Expression.Property(parameter2, property)),
                                parameter1, parameter2).Compile();
 
                        var result = memberEquivalence(m1, m2);
                        // it worked, so publish it
                        s_MemberEquivalence = memberEquivalence;
 
                        return result;
                    }
                }
                catch
                {
                    // Platform might not allow access to the property
                }
 
                // MetadataToken is not available in some contexts. Looks like this is one of those cases.
                // fallback to "IsEquivalentTo"
                Func<MemberInfo, MemberInfo, bool> fallbackMemberEquivalence = (m1param, m2param) => m1param.IsEquivalentTo(m2param);
 
                // fallback must work
                s_MemberEquivalence = fallbackMemberEquivalence;
                return fallbackMemberEquivalence(m1, m2);
            };
 
        public static bool HasSameMetadataDefinitionAs(this MemberInfo mi1, MemberInfo mi2)
        {
            return mi1.Module.Equals(mi2.Module) && s_MemberEquivalence(mi1, mi2);
        }
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static string GetIndexerName(this Type type)
        {
            Debug.Assert(type != null);
            string name = GetTypeIndexerName(type);
            if (name == null && type.IsInterface)
            {
                foreach (Type iface in type.GetInterfaces())
                {
                    name = GetTypeIndexerName(iface);
                    if (name != null)
                    {
                        break;
                    }
                }
            }
 
            return name;
        }
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        private static string GetTypeIndexerName(Type type)
        {
            Debug.Assert(type != null);
            string name = type.GetCustomAttribute<DefaultMemberAttribute>()?.MemberName;
            if (name != null)
            {
                foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
                {
                    if (p.Name == name && p.GetIndexParameters().Length != 0)
                    {
                        return name;
                    }
                }
            }
 
            return null;
        }
    }
}