【问题标题】:Assign to Property of Type Using Reflection使用反射分配给类型的属性
【发布时间】:2019-06-15 04:31:37
【问题描述】:

我正在尝试创建一个库,该库将从 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) 了解到这是由于运行时的空引用。

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

非常感谢任何见解。

【问题讨论】:

  • 我花了一段时间才发现,因为格式太奇怪了,但是由于您传递给 SetValue 的第一个参数为 null 并且您有一个非静态属性,所以会发生该错误。话虽如此,我发现整个 pi.SetValue(retObj.GetType().GetProperty(propAtt.TargetMember), convertedFVal) 的事情很可疑,因为我怀疑你想将属性本身作为目标传递,对吧?
  • 我认为这是我不理解的事情......对于一个简单的类型(字符串,十进制,int,float......)如果我这样做: pi.SetValue(retObj, convertFVal) ;然后,值设置得很好... ConvertableDecimal 是我在单位转换库中创建的一个类,并且有一个名为“BaseValue”(浮点数?)的属性被设置为一个共同商定的数字单位集,然后呈现给用户选择单位(例如英寸/毫米,加仑/升......等)所以,我需要能够填充“ConvertableDecimal”类/类型的“BaseValue”属性
  • prop.SetValue(target, value) 是等价于target.prop = value 的反射。因此,如果属性不是静态的,为什么你需要一个目标。GetProperty() 给你一个属性,而不是一个值:你需要调用 GetValue() (可能还传递一个有效的目标)来获取它的值.
  • 所以,使用上面的约定;我认为我真正想要的是“target.prop.BaseValue = value”它试图设置我难倒的“MyClass”类的“ConvertableDecimal”(类型)属性类型的“BaseValue”属性。它更深一层。如果“prop”是一个字符串,我说“target.prop =“Hello””,那么它每次都有效,但是,我没有得到一个属性的属性。 MyClass > MyProperty (typeof(ConvertableDecimal) > BaseValue = 123.456

标签: c# reflection attributes


【解决方案1】:

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

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

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

剩下的就是您要更改的对象目标是属性piretObj 之前为此确切目的创建的。

然后我把上面的转换三元函数作为值移到了这一行,因为问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;
}

【讨论】:

    猜你喜欢
    • 2012-06-17
    • 1970-01-01
    • 1970-01-01
    • 2020-11-23
    • 2018-06-05
    • 1970-01-01
    • 2015-01-21
    • 2016-05-10
    • 1970-01-01
    相关资源
    最近更新 更多