vendredi 14 juin 2019

Assign to Property of Type Using Reflection

I'm trying to create a library that will pull from a web service and map the retrieved values to any class provided by the user using attributes to decorate the target class properties. This works fine for basic types, however, some "types" are a custom type (ConvertableDecimal, ...Int, ...Float) from another library that does unit conversions. The numeric values are stored in a common property of the type called "BaseValue".

Here is an example of how a property of a class implementing properties of these types would look:

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

"OEDProperty" is an attribute class I created to decorate the properties and it takes two inputs:

  1. The xml fieldname to be mapped (e.g. "Discharge Capacity Rated") and
  2. An optional parameter called "TargetMember", "BaseValue" in this case...

Here is the mapping method:

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

The error text when trying to set the property value is: "Non-static method requires a target"

I undertstand from this post (Non-static method requires a target) that this is due to a null reference at runtime.

My question is, what options do I have for making this library work and be flexible with any user defined types that may occur in the future.

Any insights would be very much appreciated.





Aucun commentaire:

Enregistrer un commentaire