静态递归方法的问题

时间:2013-12-20 14:19:31

标签: c# reflection recursion static

我正在开发一个逻辑,我从字典键/值对中填充复合类实例。复合类将使用属性进行标记,这些属性将根据字典中的键进行映射。 一个特定的要求是,如果类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);
                }
            }
        }
    }

1 个答案:

答案 0 :(得分: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)
            {

希望这有帮助。