使用反射分配给类型的属性

时间:2019-06-15 04:31:37

标签: c# reflection attributes

我正在尝试创建一个将从Web服务提取的库,并将检索到的值映射到用户使用属性装饰目标类属性的用户提供的任何类。对于基本类型,这很好用,但是,某些“类型”是来自另一个进行单位转换的库的自定义类型(ConvertableDecimal,... Int,... Float)。数值存储在名为“ BaseValue”类型的通用属性中。

以下是实现这些类型属性的类的属性外观的一个示例:

[OEDProperty("Discharge Capacity Rated", "BaseValue")]
public ConvertableDecimal DischargeCapacity { get; set; } = new ConvertableDecimal(MeasureType.FlowRate);

“ OEDProperty”是我创建的用于装饰属性的属性类,它需要两个输入:

  1. 要映射的xml字段名称(例如“额定放电容量”)和
  2. 在这种情况下,名为“ TargetMember”,“ BaseValue”的可选参数...

以下是映射方法:

public static T Map<T> (OEDData OED, out string Errors)
{
    string mappingErrors = string.Empty;

    object retObj = Activator.CreateInstance (typeof (T)); //we'll reset this later if we need to, e.g. targeting a member
    PropertyInfo[] properties = retObj.GetType ().GetProperties();

    foreach (PropertyInfo pi in properties) 
    {
        OEDPropertyAttribute propAtt = (OEDPropertyAttribute) pi.GetCustomAttribute (typeof (OEDPropertyAttribute));
        if (propAtt != null) 
        {
            PropertyInfo piTargetMember = null;

            if (!string.IsNullOrEmpty (propAtt.TargetMember)) 
            {
                try 
                { 
                    piTargetMember = pi.PropertyType.GetProperty (propAtt.TargetMember); 
                }
                catch (Exception ex) 
                { 
                    mappingErrors += string.Format("Error locating target member \"{0}\" for type \"{1}\" when setting field \"{2}\".\r\nMake sure the target member name is spelled correctly. Target member names ARE case sensitive.\r\nError: {3}", 
                                                    propAtt.TargetMember, 
                                                    propAtt.GetType().Name, 
                                                    propAtt.Field.ToLower(), 
                                                    ex.Message); 
                }
            }

            if (propAtt.IsHeaderField) //header fields
            {
                /*snip*/
            } 
            else //fields
            {
                try 
                {
                    var fVal = OED.Fields.FirstOrDefault (f => f.FieldName.ToLower () == propAtt.Field.ToLower ()).Value;
                    var convertedFVal = (piTargetMember == null) ? ChangeType (fVal, pi.PropertyType) : ChangeType (fVal, piTargetMember.PropertyType);

                    if (piTargetMember == null) 
                    { 
                        pi.SetValue(retObj, convertedFVal); 
                    } 
                    else
                    {
                        pi.SetValue(retObj.GetType().GetProperty(propAtt.TargetMember), convertedFVal);
                        //error happens here
                        //error text: Non-static method requires a target
                    }
                }
                catch (Exception ex) 
                { 
                    mappingErrors += string.Format("Unable to map oed field value: \"{0}\".\r\nError: {1}", propAtt.Field.ToLower (), ex.Message); 
                }
            }
        }
    }

    Errors = mappingErrors;

    return (T) retObj;
}

尝试设置属性值时的错误文本为:“非静态方法需要目标”

我从这篇文章(Non-static method requires a target)中了解到,这是由于运行时引用为空所致。

我的问题是,我有什么选择可以使该库正常工作,并可以灵活处理将来可能出现的任何用户定义类型。

任何见解将不胜感激。

1 个答案:

答案 0 :(得分:0)

对于有问题的那一行,我建议进行以下更改:

piTargetMember.SetValue(pi.GetValue(retObj), ChangeType(fVal, piTargetMember.PropertyType));

首先,我将PropertyInfo从目标参数移到要设置其值的属性。从周围的代码中,我相信这是预期的操作。

这将使您想要更改的对象目标成为先前为该确切目的而创建的pi的属性retObj

然后,我将转换三元函数从上面移到该行中作为值,因为询问两次piTargetMember == null是否有意义。


编辑:

如果您的T的构造函数没有创建属性实例,则您可能还必须在代码中执行该操作,这会将该行更改为三行:

object propInstance = Activator.CreateInstance(pi.PropertyType);
piTargetMember.SetValue(propInstance, ChangeType(fVal, piTargetMember.PropertyType));
pi.SetValue(retObj, propInstance);

完整代码

在解析代码时,我简化了许多格式。因此,我将整个代码作为示例发布:

public static T Map<T>(OEDData OED, out string Errors)
{
    string mappingErrors = string.Empty;

    object retObj = Activator.CreateInstance(typeof(T)); //we'll reset this later if we need to, e.g. targeting a member

    foreach (PropertyInfo pi in typeof(T).GetProperties()) 
    {
        if (pi.GetCustomAttribute(typeof(OEDPropertyAttribute)) is OEDPropertyAttribute propAtt) 
        {
            PropertyInfo piTargetMember = null;

            if (!string.IsNullOrEmpty(propAtt.TargetMember)) 
            {
                try 
                { 
                    piTargetMember = pi.PropertyType.GetProperty(propAtt.TargetMember); 
                }
                catch (Exception ex) 
                { 
                    mappingErrors += $"Error locating target member \"{propAtt.TargetMember}\" for type \"{propAtt.GetType().Name}\" when setting field \"{propAtt.Field.ToLower()}\"." + 
                                     $"\r\nMake sure the target member name is spelled correctly. Target member names ARE case sensitive.\r\nError: {ex.Message}\r\n"; 
                }
            }

            if (propAtt.IsHeaderField) //header fields
            {
                /*snip*/
            } 
            else //fields
            {
                try 
                {
                    var fVal = OED.Fields.FirstOrDefault((f) => string.Equals(f.FieldName, propAtt.Field, StringComparison.CurrentCultureIgnoreCase))?.Value;

                    if (piTargetMember == null)
                    {
                        pi.SetValue(retObj, ChangeType(fVal, pi.PropertyType));
                    }
                    else
                    {
                        object propInstance = pi.GetValue(retObj);
                        if (propInstance == null)
                        {
                            // construct the value which is the property pointed to by 'pi'
                            propInstance = Activator.CreateInstance(pi.PropertyType);
                            pi.SetValue(retObj, propInstance);
                        }
                        piTargetMember.SetValue(propInstance, ChangeType(fVal, piTargetMember.PropertyType));
                    }
                }
                catch (Exception ex) 
                { 
                    mappingErrors += $"Unable to map oed field value: \"{propAtt.Field.ToLower()}\".\r\nError: {ex.Message}\r\n";
                }
            }
        }
    }

    Errors = mappingErrors;

    return (T) retObj;
}