Как проверить, что тип может быть передан методу generi c в C#, а также восстановить типы параметров generi c на основе переданных аргументов - PullRequest
0 голосов
/ 18 февраля 2020

Позвольте мне попытаться объяснить, что я имею в виду. Например, у меня есть следующий код:

public static class MyClass
{
   public static void DoSmthWithCollection<T>(IEnumerable<T> collection)
   {
       ...
   }
}

Конечно, компилятор позволил бы передать ему объект типа List<int>. Более того, компилятор выяснит, что тип T для метода разрешен в тип int на основе типа аргумента.

Мне нужно делать то же самое только динамически, используя System.Reflection или Roslyn. Скажем, я получил MethodInfo за метод, я получил обобщенный c ParameterInfo. Есть ли какой-нибудь простой способ выяснить, что объект типа typeof(List<int>) действительно подходит в качестве аргумента метода и что при его передаче метод разрешает тип T в int? И я говорю о некотором относительно простом решении без изучения типов и подтипов и общих ограничений типа c. Я думаю, что по крайней мере у Рослина должен быть такой, поскольку именно этим занимается компилятор C#.

Спасибо за вашу помощь.

Ответы [ 2 ]

0 голосов
/ 21 февраля 2020

Я думаю, что нашел правильное решение, используя некоторые идеи из примера Джереми Лэйкмана ниже. Он даже учитывает ограничения (хотя я думаю, что это будет sh для рекурсивных ограничений).

    public static bool CheckConcreteTypeSatisfiesGenericParamConstraints(this Type concreteType, Type genericParamType)
    {
        bool hasReferenceTypeConstraint = genericParamType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint);
        bool hasNewConstraint =
            genericParamType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint);

        bool isNonNullable = genericParamType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint);

        if (hasReferenceTypeConstraint)
        {
            if (concreteType.IsValueType)
                return false;
        }
        else if (isNonNullable && !concreteType.IsValueType)
        {
            return true;
        }

        if (hasNewConstraint)
        {
            ConstructorInfo constrInfo = concreteType.GetConstructor(new Type[] { });

            if (constrInfo != null)
                return false;
        }

        Type[] constraintTypes = genericParamType.GetGenericParameterConstraints();

        if (constraintTypes == null)
            return true;

        Type[] concreteTypeSuperTypes = concreteType.GetSelfSuperTypesAndInterfaces().Distinct().ToArray();

        foreach(Type constraintType in constraintTypes)
        {
            if (!concreteTypeSuperTypes.Contains(constraintType))
            {
                return false;
            }
        }

        return true;
    }


    public static IEnumerable<Type> GetSelfSuperTypesAndInterfaces(this Type type)
    {
        if (type != null)
        {
            yield return type;
        }

        if (type.BaseType != null)
        {
            foreach (var superType in type.BaseType.GetSelfSuperTypesAndInterfaces())
            {
                yield return superType;
            }
        }

        foreach (var interfaceType in type.GetInterfaces())
        {
            foreach (var superInterface in interfaceType.GetSelfSuperTypesAndInterfaces())
            {
                yield return superInterface;
            }
        }
    }

    public class GenericParamInfo
    {
        public Type GenericParamType { get; }

        public Type ConcreteType { get; set; }

        public GenericParamInfo(Type genericParamType)
        {
            GenericParamType = genericParamType;
        }
    }

    // returns false if cannot resolve
    public static bool ResolveType
    (
        this Type argToResolveType, 
        Type genericArgType, 
        IEnumerable<GenericParamInfo> genericTypeParamsToConcretize)
    {
        if (genericArgType.IsGenericParameter)
        {
            GenericParamInfo foundParamInfo = genericTypeParamsToConcretize.Single(t => t.GenericParamType == genericArgType);

            if (!argToResolveType.CheckConcreteTypeSatisfiesGenericParamConstraints(foundParamInfo.GenericParamType))
            {
                return false;
            }

            foundParamInfo.ConcreteType = argToResolveType;

            return true;
        }
        else if (genericArgType.IsGenericType)
        {
            var matchingSuperType =
                argToResolveType.GetSelfSuperTypesAndInterfaces()
                                .Distinct()
                                .Where(arg => arg.IsGenericType)
                                .Single(arg => arg.GetGenericTypeDefinition() == genericArgType.GetGenericTypeDefinition());

            if (matchingSuperType == null)
                return false;

            Type[] concreteArgs = matchingSuperType.GetGenericArguments();
            Type[] genericArgs = genericArgType.GetGenericArguments();

            foreach((Type concrArgType, Type genArgType) in concreteArgs.Zip(genericArgs, (c, g) => (c, g)))
            {
                if (!concrArgType.ResolveType(genArgType, genericTypeParamsToConcretize))
                    return false;
            }

            return true;
        }

        return true;

    }

Ниже я покажу, как проверить это на 3 методах = DoSmth1<T1, T2>(IEnumerable<KeyValuePair<T1, T2>> collection), DoSmth2<T>(T val) и DoSmth3<T>(IEnumerable<T> val):

    public static void DoSmth1<T1, T2>(IEnumerable<KeyValuePair<T1, T2>> collection)
        where T2 : struct
    {

    }

    public static void DoSmth2<T>(T val)
    {

    }

    public static void DoSmth3<T>(IEnumerable<T> val)
    {

    }

    static (bool, GenericParamInfo[]) TestResolveMethodType(Type argToResolve, string methodName)
    {
        MethodInfo methodInfo = typeof(Program).GetMethod(methodName);

        //assuming that we always try to resolve the first method parameter for simplicity
        Type genericArgType = methodInfo.GetParameters()[0].ParameterType;

        GenericParamInfo[] typesToConcretize =
            methodInfo.GetGenericArguments().Select(t => new GenericParamInfo(t)).ToArray();

        bool result = argToResolve.ResolveType(genericArgType, typesToConcretize);

        return (result, typesToConcretize);
    }


    static void Main(string[] args)
    {
        (bool result1, GenericParamInfo[] concretizedTypes1) = 
            TestResolveMethodType(typeof(Dictionary<int, double>), nameof(DoSmth1));

        (bool result2, GenericParamInfo[] concretizedTypes2) =
            TestResolveMethodType(typeof(int), nameof(DoSmth2));

        (bool result3, GenericParamInfo[] concretizedTypes3) =
            TestResolveMethodType(typeof(List<int>), nameof(DoSmth3));
    }
0 голосов
/ 20 февраля 2020

Я бы не назвал это простым, но я написал что-то, что, кажется, работает.

        private IEnumerable<Type> BaseTypes(Type t)
        {
            do
            {
                yield return t;
                t = t.BaseType;
            } while (t != null);
        }


            var method = new Action<IEnumerable<object>>(MyClass.DoSmthWithCollection).Method.GetGenericMethodDefinition();
            var testParms = new Type[] { typeof(List<int>) };

            var methodParms = method.GetParameters();
            var methodTypes = method.GetGenericArguments();
            var actualTypes = new Type[methodTypes.Length];

            for (var parmIndex = 0; parmIndex < methodParms.Length; parmIndex++)
            {
                var methodParmType = methodParms[parmIndex].ParameterType;
                if (!methodParmType.ContainsGenericParameters)
                    continue;

                var methodParmBaseType = methodParmType.GetGenericTypeDefinition();
                var testParmType = testParms[parmIndex];
                IEnumerable<Type> compareTypes = methodParmType.IsInterface ? testParmType.GetInterfaces() : BaseTypes(testParmType);
                var match = compareTypes.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == methodParmBaseType).Single();
                var testArgs = match.GetGenericArguments();
                var parmArgs = methodParmType.GetGenericArguments();
                for (var i = 0; i < parmArgs.Length; i++)
                {
                    if (parmArgs[i].IsGenericMethodParameter)
                    {
                        for(var idx = 0; idx < methodTypes.Length; idx++)
                            if (methodTypes[idx] == parmArgs[i])
                            {
                                actualTypes[idx] = testArgs[i];
                                break;
                            }
                    }
                }
            }

            var genericMethod = method.MakeGenericMethod(actualTypes);

Я надеюсь, что есть более простой способ, но я еще не нашел его.

...