0

I am using Mono.Cecil to automatically generate (lots of, simple, generic) factory methods providing a convenient API for a library. The factories are generated for properties marked with a special custom attribute. To generate them, I must know the type of such property. The non-generic case is simple:

ModuleDefinition module = /* obtained from ReadAssembly */
foreach (var type in module.Types)
    if (/* type is marked with the right attribute */)
        foreach (var prop in type.Properties)
            if (/* prop is marked with the right attribute */)
                GenerateFactory(type, prop, prop.PropertyType);

However, some of the types marked are in fact generics. In this case, the attribute on the type contains the generic arguments for which the factory should be made, like this:

[EmitFactories(typeof(int))]
public class Class<T>
{
    [MagicProperty]
    T Property { get; set; }
}

(here, I want the factory to be made for Class<int>.Property). I am handling this case by making type a GenericInstanceType. However, I cannot get to the type of the property -- to enumerate type.Properties I need to first call Resolve(), which loses all generic information. The property type is then T (instead of int), which of course makes later code fail miserably.

Mono.Cecil has GenericInstanceType and GenericInstanceMethod, but there is no equivalent for properties. I tried using module.Import(prop.PropertyType, type) (giving type as the generic parameter provider), but this doesn't work.

Do you have any ideas on how I can resolve the actual property type? Note, that it can be completely unrelated to T, T itself, or have T burried inside (e.g., List<T>). Ideally, it would work given type as a TypeReference -- this way I would not have to write separate code for the non-generic and generic cases.

Grzegorz Herman
  • 1,875
  • 11
  • 22
  • There is too little code in your example... You could create a `Dictionary` with the key the generic arguments `T` and as the value the replacement (`int` in this case). Then when you encounter a generic class, you add to this dictionary, and you pass it to the `GenerateFactory`, that does the replacements... – xanatos May 16 '15 at 19:10
  • @xanatos: What code would you find appropriate? Looking up the attributes is straightforward and not interesting here. The actual factory generation has no influence on this question either: I need to get the actual PropertyType first. – Grzegorz Herman May 16 '15 at 19:13
  • something that I can compile :-) – xanatos May 16 '15 at 19:14
  • @xanatos: Unfortunately, the type might not only be `T`, but possibly something containing `T` inside (i.e. `List`). I would rather avoid recursively traversing the whole thing... – Grzegorz Herman May 16 '15 at 19:14
  • @xanatos: I can provide that in a few minutes. Are you sure it is not going to only add noise to the question? – Grzegorz Herman May 16 '15 at 19:15
  • No I don't need it anymore probably... already reconstructed. But this thing of the `List`, you should have written it! :-) Because the `T` simple case is simple, the problem is with the corner cases! – xanatos May 16 '15 at 19:20

1 Answers1

1

Based on https://stackoverflow.com/a/16433452/613130, that is probably based on https://groups.google.com/d/msg/mono-cecil/QljtFf_eN5I/YxqLAk5lh_cJ, it should be:

using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Rocks;

namespace Utilities
{
    public class EmitFactoriesAttribute : Attribute
    {
        public readonly Type[] Types;

        public EmitFactoriesAttribute()
        {
        }

        public EmitFactoriesAttribute(params Type[] types)
        {
            Types = types;
        }
    }

    public class MagicPropertyAttribute : Attribute
    {
    }

    public static class EmitFactories
    {
        public static void WorkOnAssembly(string path)
        {
            AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly("ConsoleApplication4.exe");

            ModuleDefinition module = assembly.MainModule;

            TypeDefinition emitFactoriesAttribute = module.Import(typeof(EmitFactoriesAttribute)).Resolve();
            TypeDefinition magicPropertyAttribute = module.Import(typeof(MagicPropertyAttribute)).Resolve();

            foreach (TypeDefinition type in module.Types)
            {
                CustomAttribute emitFactory = type.CustomAttributes.SingleOrDefault(x => x.AttributeType.MetadataToken == emitFactoriesAttribute.MetadataToken);

                if (emitFactory == null)
                {
                    continue;
                }

                TypeReference typeRef = type;

                TypeReference[] replacementTypes;

                if (emitFactory.ConstructorArguments.Count != 0)
                {
                    var temp = ((CustomAttributeArgument[])emitFactory.ConstructorArguments[0].Value);
                    replacementTypes = Array.ConvertAll(temp, x => (TypeReference)x.Value);
                }
                else
                {
                    replacementTypes = new TypeReference[0];
                }

                if (replacementTypes.Length != type.GenericParameters.Count)
                {
                    throw new NotSupportedException();
                }

                if (replacementTypes.Length != 0)
                {
                    typeRef = typeRef.MakeGenericInstanceType(replacementTypes);
                }

                foreach (PropertyDefinition prop in type.Properties)
                {
                    CustomAttribute magicProperty = prop.CustomAttributes.SingleOrDefault(x => x.AttributeType.MetadataToken == magicPropertyAttribute.MetadataToken);

                    if (magicProperty == null)
                    {
                        continue;
                    }

                    MethodReference getter = prop.GetMethod;
                    MethodReference setter = prop.SetMethod;

                    if (replacementTypes.Length != 0)
                    {
                        if (getter != null)
                        {
                            getter = getter.MakeHostInstanceGeneric(replacementTypes);
                        }

                        if (setter != null)
                        {
                            setter = setter.MakeHostInstanceGeneric(replacementTypes);
                        }
                    }
                }
            }
        }
    }

    public static class TypeReferenceExtensions
    {
        // https://stackoverflow.com/a/16433452/613130
        public static MethodReference MakeHostInstanceGeneric(this MethodReference self, params TypeReference[] arguments)
        {
            var reference = new MethodReference(self.Name, self.ReturnType, self.DeclaringType.MakeGenericInstanceType(arguments))
            {
                HasThis = self.HasThis,
                ExplicitThis = self.ExplicitThis,
                CallingConvention = self.CallingConvention
            };

            foreach (var parameter in self.Parameters)
                reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));

            foreach (var generic_parameter in self.GenericParameters)
                reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));

            return reference;
        }
    }

    // Test
    [EmitFactories(typeof(int), typeof(long))]
    public class Class<TKey, TValue>
    {
        [MagicProperty]
        Dictionary<TKey, TValue> Property1 { get; set; }

        [MagicProperty]
        List<TValue> Property2 { get; set; }
    }
}

You hadn't defined how EmitFactoriesAttribute was, so I have written it as a EmitFactoriesAttribute(params Type[] types), to be able to accept multiple substitutions for cases like Class<TKey, TValue>.

In the end I'm not manipulating directly the property: I'm manipulating its getter and setter.

I'm not able to test it, because I don't have the factory...

Community
  • 1
  • 1
xanatos
  • 109,618
  • 12
  • 197
  • 280