Mono.Cecil: How to get all base types and interfaces with resolved generic arguments

Following code is the result of a discussion in the mono-cecil user group. If you don’t know what Mono.Cecil is, this post probably does not contain much information for you. However, you could check out this great library and maybe come back later.

Limitations

At the moment it does not resolve generic arguments recursively, so for IDictionrary<string, int> it will return also IEnumerable<KeyValuePair<TKey, TValue>> instead of correct IEnumerable<KeyValuePair<string, int>>.

Disclaimer

I admin that the code is not as nice as it should be and it definitely contains some nasty bugs, which will appear under some esoteric circumstances, however on coming deadline is pushing me to do a few technical debts. Despite that I think some other people might find it helpful, at least as a starting point to their own implementation.

I would also like to thank to Jb Evains for creating and maintaining Cecil.

        public static IEnumerable<TypeReference> GetBaseTypes(
			this TypeDefinition type,
			bool includeIfaces)
        {
            Contract.Requires(type != null);
            Contract.Requires(
				type.IsInterface == false,
				"GetBaseTypes is not valid for interfaces");

            var result = new List<TypeReference>();
            var current = type;
            var mappedFromSuperType = new List<TypeReference>();
            var previousGenericArgsMap =
				GetGenericArgsMap(
					type,
					new Dictionary<string, TypeReference>(),
					mappedFromSuperType);
            Contract.Assert(mappedFromSuperType.Count == 0);

            do
            {
                var currentBase = current.BaseType;
                if (currentBase is GenericInstanceType)
                {
                    previousGenericArgsMap =
							GetGenericArgsMap(
								current.BaseType,
								previousGenericArgsMap,
								mappedFromSuperType);
                    if (mappedFromSuperType.Any())
                    {
                        currentBase = ((GenericInstanceType)currentBase)
							.ElementType.MakeGenericInstanceType(
								previousGenericArgsMap
									.Select(x => x.Value)
									.ToArray());
                        mappedFromSuperType.Clear();
                    }
                }
                else
                {
                    previousGenericArgsMap =
						new Dictionary<string, TypeReference>();
                }

                result.Add(currentBase);
                current = current.BaseType.SafeResolve(
                    string.Format(
						CannotResolveMessage,
						current.BaseType.FullName,
						current.FullName));
                if (includeIfaces)
                {
                    result.AddRange(BuildIFaces(current, previousGenericArgsMap));
                }
            }
            while (current.IsEqual(typeof(object)) == false);

            return result;
        }

        private static IEnumerable<TypeReference> BuildIFaces(
			TypeDefinition type,
			IDictionary<string, TypeReference> genericArgsMap)
        {
            var mappedFromSuperType = new List<TypeReference>();
            foreach (var iface in type.Interfaces)
            {
                var result = iface;
                if (iface is GenericInstanceType)
                {
                    var map =
						GetGenericArgsMap(
							iface,
							genericArgsMap,
							mappedFromSuperType);
                    if (mappedFromSuperType.Any())
                    {
                        result = ((GenericInstanceType)iface).ElementType
                            .MakeGenericInstanceType(
								map.Select(x => x.Value).ToArray());
                    }
                }

                yield return result;
            }
        }

        private static IDictionary<string, TypeReference> GetGenericArgsMap(
            TypeReference type,
            IDictionary<string, TypeReference> superTypeMap,
            IList<TypeReference> mappedFromSuperType)
        {
            var result = new Dictionary<string, TypeReference>();
            if (type is GenericInstanceType == false)
            {
                return result;
            }

            var genericArgs = ((GenericInstanceType)type).GenericArguments;
            var genericPars = ((GenericInstanceType)type)
				.ElementType.SafeResolve(CannotResolveMessage).GenericParameters;

            /*
             * Now genericArgs contain concrete arguments for the generic
			 * parameters (genericPars).
             *
             * However, these concrete arguments don't necessarily have
			 * to be concrete TypeReferences, these may be referencec to
			 * generic parameters from super type.
             *
             * Example:
             *
             *      Consider following hierarchy:
             *          StringMap<T> : Dictionary<string, T>
             *
             *          StringIntMap : StringMap<int>
             *
             *      What would happen if we walk up the hierarchy from StringIntMap:
             *          -> StringIntMap
             *              - here dont have any generic agrs or params for StringIntMap.
             *              - but when we reesolve StringIntMap we get a
			 *					reference to the base class StringMap<int>,
             *          -> StringMap<int>
             *              - this reference will have one generic argument
			 *					System.Int32 and it's ElementType,
             *                which is StringMap<T>, has one generic argument 'T'.
             *              - therefore we need to remember mapping T to System.Int32
             *              - when we resolve this class we'll get StringMap<T> and it's base
             *              will be reference to Dictionary<string, T>
             *          -> Dictionary<string, T>
             *              - now *genericArgs* will be System.String and 'T'
             *              - genericPars will be TKey and TValue from Dictionary
			 * 					declaration Dictionary<TKey, TValue>
             *              - we know that TKey is System.String and...
             *              - because we have remembered a mapping from T to
			 *					System.Int32 and now we see a mapping from TValue to T,
             *              	we know that TValue is System.Int32, which bring us to
			 *					conclusion that StringIntMap is instance of
             *          -> Dictionary<string, int>
             */              

            for (int i = 0; i < genericArgs.Count; i++)
            {
                var arg = genericArgs[i];
                var param = genericPars[i];
                if (arg is GenericParameter)
                {
                    TypeReference mapping;
                    if (superTypeMap.TryGetValue(arg.Name, out mapping) == false)
                    {
                        throw new Exception(
                            string.Format(
                                "GetGenericArgsMap: A mapping from supertype was not found. " +
                                "Program searched for generic argument of name {0} in supertype generic arguments map " +
                                "as it should server as value form generic argument for generic parameter {1} in the type {2}",
                                arg.Name,
                                param.Name,
                                type.FullName));
                    }

                    mappedFromSuperType.Add(mapping);
                    result.Add(param.Name, mapping);
                }
                else
                {
                    result.Add(param.Name, arg);
                }
            }

            return result;
        }

Post a Comment

Your email is never published nor shared. Required fields are marked *