1
votes

I was using 3rd-party package to a .Net cms, kind of ORM from internal data representation to a POCO. To get it worked I needed to take it's source code from GitHub to debug and see what is wrong. I've found that there is reflection MethodInfo.Invoke call to a method that uses yield return. The return from this method is supposed to be assigned to a POCO's property with property.SetValue(). And this very call returned null that, being set to a property, caused invalid result of mapping.

I changed method's logic a bit to compose IEnumerable<> first and then return it as it is with ordinary return, no yield - then everything started working properly.

My question is - since this package was downloaded and used by other people so it is considered to be working, putting aside my source code change - perhaps there is some approach to deal with Invoked methods that return with yield? I know that yield returns an ienumerator (state machine) that is to be used in foreach-like loops over some IEnumerable but it is for direct IEnumerable generator method call, it was the first time when I saw reflection-call of such method. Thanks.

UPDATE: Thanks @GeorgeVovos. The plugin is https://github.com/AliSheikhTaheri/Archetype-Mapper. The code that caused problem is:

  1. Calling part:

        public void SetPropertyValue<T>(object fromObject, PropertyInfo property, T model, IUmbracoMapper mapper)
    {
        var fieldsets = GetArchetypeModel(fromObject);
    
        if (fieldsets != null)
        {
            var type = property.PropertyType.GetGenericArguments()[0];
            var method = GetType().GetMethod("GetItems", BindingFlags.NonPublic | BindingFlags.Instance);
            var genericMethod = method.MakeGenericMethod(type);
            var items = genericMethod.Invoke(this, new object[] { fieldsets, mapper });
            property.SetValue(model, items);
        }
    } 
    
  2. Method:

        private IEnumerable<T> GetItems<T>(IEnumerable<ArchetypeFieldsetModel> fieldsets, IUmbracoMapper mapper)
    {
        foreach (var fieldset in fieldsets)
        {
            // Instantiate the T
            var instance = (T)Activator.CreateInstance(typeof(T));
    
            // make a dictionary of property alias and value
            var dictionary = fieldset.Properties.ToDictionary(property => FirstToUpper(property.Alias), property => property.Value);
    
            // If fieldset name is the same as instance type then lets map it to the instance
            if (instance.GetType().Name.ToLower() == fieldset.Alias.ToLower())
            {
                mapper.Map(dictionary, (object)instance);
            }
            else // if not then lets find a property with the same name as fieldset name
            {
                var property = instance.GetType().GetProperties().FirstOrDefault(x => x.Name.ToLower() == fieldset.Alias.ToLower());
                if (property != null)
                {
                    var propertyClass = Activator.CreateInstance(property.PropertyType);
                    mapper.Map(dictionary, propertyClass);
    
                    var propertyInfo = instance.GetType().GetProperty(property.Name);
                    propertyInfo.SetValue(instance, Convert.ChangeType(propertyClass, propertyInfo.PropertyType));
                }
            }
    
            yield return instance;
        }
    }
    

I understood your answer. Looks like when I started to use the plugin my code (model) had incorrectly defined properties decorated with attribute for which plugin code maps data from CMS. As I understood original plugin code provides lazy data obtaining while my changes made it eager. But if I were define properties as ienumerables at first I would not come to the problem.

Thank a lot!

1
Did you ask through an issue at the project?Jon Hanna
No. I got it working and used in my app. I am mostly interested to understand how reflection-invoked call supposed to work on methods that return with yield.The Yur
@TheYur See my answer and provide more details about your problem.What was the github project?How can we reproduce your issue?George Vovos

1 Answers

1
votes

It doesn't matter how a method is called(using reflection or not).
It works as expected.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass m = new MyClass();

            MethodInfo method = m.GetType().GetMethod("MyMethod");
            var result = (IEnumerable<string>)method.Invoke(m, null);
            foreach (var item in result)
                Console.WriteLine("Printing:" + (item ?? "null"));

            Console.ReadLine();
        }
    }

    public class MyClass
    {
        public IEnumerable<string> MyMethod()
        {
            Console.WriteLine("Returning null");
            yield return null;
            Console.WriteLine("Returning 111");
            yield return "111";
            Console.WriteLine("Returning 222");
            yield return "222";
            Console.WriteLine("Returning 333");
            yield return "333";
        }
    }
}

This prints:
enter image description here

Can you provide a code sample to see exactly what was your problem?