【问题标题】:Issue with a static recursive method静态递归方法的问题
【发布时间】:2014-01-09 10:24:55
【问题描述】:

我正在研究从字典键/值对填充复合类实例的逻辑。复合类将用属性标记,这些属性将映射到字典中的键。 一个具体要求是,如果类 C1 具有类 C2 类型的属性,但字典中没有要映射的类 C2 的属性对,则 C2 应设置为 null。否则,如果 C2 至少有一个属性可以映射,则必须实例化 C1 的 C2 属性。 我写了一个递归函数来实现这个逻辑。该要求未按预期工作。我正在使用一个标志 isInstanceValuePresent 来检查是否可以映射 C2 的至少一个属性。否则,它的错误值应该告诉我必须将 null 分配给类 C1 的 C2 属性。如果有人可以帮助我理解逻辑失败的原因以及正确的解决方案是什么,我将非常感激。下面是静态递归方法:

/// <summary>
    /// Populates the given instance object with the supplied source dictionary values
    /// </summary>
    /// <param name="modelInstance">The object whose properties are to be initialized with the data</param>
    /// <param name="source">The source dictionary containing Schema(Keys) and corresponding Values</param>
    private static void PopulateModelInstance(object modelInstance, IDictionary<string, string> source)
    {
        bool isInstanceValuePresent = false;

        foreach (PropertyInfo propInfo in modelInstance.GetType().GetProperties())
        {
            //Identify Custom attribute 
            DataMappingKeyAttribute attribute = (DataMappingKeyAttribute)Attribute.GetCustomAttribute(propInfo, typeof(DataMappingKeyAttribute));

            if (attribute != null && !string.IsNullOrEmpty(attribute.MappingKey))
            {
                if (propInfo.PropertyType.IsPrimitive || propInfo.PropertyType.Equals(typeof(string)))
                {
                    string sourceKey = attribute.MappingKey;

                    if (source.ContainsKey(sourceKey))
                    {
                        isInstanceValuePresent = true;

                        // Get propInfo attribute value from Dictionary
                        //var propertySourceValue = source[(propInfo.PropertyType.GetCustomAttribute(typeof(DataMappingKeyAttribute)) as DataMappingKeyAttribute).MappingKey];
                        string sourceValue = source[attribute.MappingKey];

                        // Set propInfo value on the model instance
                        if (CanChangeType(sourceValue, propInfo.PropertyType) && propInfo.CanWrite && (!propInfo.PropertyType.IsClass || propInfo.PropertyType.Equals(typeof(string))))
                            propInfo.SetValue(modelInstance, Convert.ChangeType(sourceValue, propInfo.PropertyType), null);
                    }
                }
            }


            if (propInfo.PropertyType.IsClass && !propInfo.PropertyType.Equals(typeof(string)) && propInfo.CanWrite)
            {
                isInstanceValuePresent = false;
                object referenceTypeInstance = Activator.CreateInstance(propInfo.PropertyType);

                PopulateModelInstance(referenceTypeInstance, source);

                if (isInstanceValuePresent == false)
                {
                    propInfo.SetValue(modelInstance, null, null);
                    referenceTypeInstance = null;
                }
                else
                {
                    propInfo.SetValue(modelInstance, referenceTypeInstance, null);
                }
            }
        }
    }

【问题讨论】:

  • 如果有人能帮我优化功能,我什至感激不尽。
  • 逻辑如何失败?它没有执行,或者您的结果如何不符合预期?

标签: c# reflection recursion static


【解决方案1】:

您的代码的一个主要问题是使用了变量 isInstanceValuePresent。在递归调用 PopulateModelInstance 之前,您将变量设置为 false,然后在方法返回后测试该值。不幸的是,这个变量是一个局部变量,驻留在堆栈上,因此对于每个调用都是局部的。它不会反映您在递归调用中设置的值。

我有一个建议,你可以如何改变你的方法。您可以传入要填充的对象的类型,而不是传入要填充的对象。使用您已经实现的相同逻辑,如果您找到可以设置的属性值,您只会实例化这种类型的对象。然后该方法将实例传回。如果没有找到要设置的属性,则该方法传回 null。

private static Object CreateAndPopulateModelInstance(Type modelInstanceType, IDictionary<string, string> source)
{
    // this variable will hold the reference to the instance that is to be
    // populated. It will only hold a value, if a property is found that
    // can be populated.
    Object modelInstance = null;

    foreach (PropertyInfo propInfo in modelInstanceType.GetProperties())
    {
        //Identify Custom attribute 
        DataMappingKeyAttribute attribute =  DataMappingKeyAttribute)Attribute.GetCustomAttribute(propInfo, typeof(DataMappingKeyAttribute));

        if (attribute != null && !string.IsNullOrEmpty(attribute.MappingKey))
        {
            if (propInfo.PropertyType.IsPrimitive || propInfo.PropertyType.Equals(typeof(string)))
            {
                string sourceKey = attribute.MappingKey;

                if (source.ContainsKey(sourceKey))
                {
                    // Get propInfo attribute value from Dictionary
                    //var propertySourceValue = source[(propInfo.PropertyType.GetCustomAttribute(typeof(DataMappingKeyAttribute)) as DataMappingKeyAttribute).MappingKey];
                    string sourceValue = source[attribute.MappingKey];

                    // Set propInfo value on the model instance
                    if (CanChangeType(sourceValue, propInfo.PropertyType) && propInfo.CanWrite && (!propInfo.PropertyType.IsClass || propInfo.PropertyType.Equals(typeof(string))))
                    {
                        // create instance if necessary
                        if (modelInstance == null)
                            modelInstance = Activator.CreateInstance(modelInstanceType);

                        propInfo.SetValue(modelInstance, Convert.ChangeType(sourceValue, propInfo.PropertyType), null);
                    }
                }
            }
        }
        else if (propInfo.PropertyType.IsClass && !propInfo.PropertyType.Equals(typeof(string)) && propInfo.CanWrite)
        {
            Object propertyValue = CreateAndPopulateModelInstance(propInfo.PropertyType, source);
            if (propertyValue != null)
            {
                // create instance if necessary
                if (modelInstance == null)
                    modelInstance = Activator.CreateInstance(modelInstanceType);

                // set property value
                propInfo.SetValue(modelInstance, propertyValue, null);
            }
        }
    }

    return modelInstance;
}

我重命名了方法以反映创建对象的事实(如有必要)。 在设置属性之前,该方法会检查 modelInstance 是否已经实例化,如果没有实例化传入类型的对象。

如果方法传回 null,您就知道您的属性值无法实例化,因为它没有任何可以初始化的属性。无论递归有多深,这都应该有效。

我还没有测试过这段代码(甚至没有编译过),所以那里可能存在语法错误。逻辑应该没问题。

您必须更改对该方法的初始调用,或者添加第三个参数来初始化 modelInstance 变量,如下所示:

private static Object CreateAndPopulateModelInstance(Object instance, Type modelInstanceType, IDictionary<string, string> source)
{
    // this variable will hold the reference to the instance that is to be
    // populated. It will only hold a value, if a property is found that
    // can be populated.
    Object modelInstance = instance;

递归调用将如下所示:

            Object propertyValue = CreateAndPopulateModelInstance(null, propInfo.PropertyType, source);
            if (propertyValue != null)
            {

希望这会有所帮助。

【讨论】:

  • 感谢@roadkill,您的解决方案运行良好。我非常感谢您为解释解决方案所做的努力。
猜你喜欢
  • 2012-10-22
  • 2014-04-19
  • 1970-01-01
  • 1970-01-01
  • 2015-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多