首页 > 解决方案 > 使用反射分配给类型的属性

问题描述

我正在尝试创建一个库,该库将从 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;
}

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

我从这篇文章(非静态方法需要一个目标)中了解到这是由于运行时的空引用。

我的问题是,我有哪些选项可以使这个库正常工作并灵活处理将来可能出现的任何用户定义的类型。

任何见解将不胜感激。

标签: c#reflectionattributes

解决方案


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

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;
}

推荐阅读