简体   繁体   中英

How to use C# Reflection to set properties and fields with generic code?

My code wants to iterate a Dictionary that contains both FieldInfo and PropertyInfo of a type, and use that to map the values from one object to another. For example:

   public static void MapFieldsAndProperties(object source, object target)
    {
        Dictionary<string, MemberInfo> target_properties = ClassUtils.GetPropertiesAndFields(target);
        Dictionary<string, MemberInfo> source_properties = ClassUtils.GetMatchingPropertiesAndFields(target_properties.Keys, source);

        foreach (var entry in source_properties)
        {
            var sourceProperty = entry.Value;
            var targetProperty = target_properties[entry.Key];
            // for now, match datatypes directly
            if (dataTypesMatch(source, target))
            {
                var sourceValue = sourceProperty.GetValue(source);
                try
                {
                    targetProperty.SetValue(target, sourceValue);
                }
                catch (TargetException e)
                {
                    LOG.ErrorFormat("unable to set value {0} for property={1}, ex={2}", sourceValue, targetProperty, e);
                }
            }
        }
    }

The problems with the above are: 1) The dataTypesMatch() function requires 2 different method signatures one for FieldInfo and one for PropertyInfo (and then to check the type of each and cast appropriately to dispatch to correct function). This is because to check Field data type uses FieldInfo.FieldType while data type for Property uses PropertyInfo.PropertyType .

2) Even though both FieldInfo and PropertyInfo have SetValue and GetValue methods, they do not derive from a common parent class, so it again requires a cast. (Maybe Dynamic would take care of this problem?)

Is there a solution which allows treating these 2 types of MemberInfo objects generically to check DataType and to Get/SetValue?

为什么不只修改方法以接受Type类型的两个参数,并相应地传递FieldInfo.FieldTypePropertyInfo.PropertyType

Since there didn't seem to be any native solution, I wrapped the PropertyInfo and FieldInfo objects in an interface which enabled the client code to use their relevant properties and methods without having to branch and cast them in the main body of the code.

   public interface IGetterSetter
    {
        Type DataType { get; }
        string Name { get; }
        MemberInfo UnderlyingMember { get; }
        bool CanRead { get; }
        bool CanWrite { get; }

        object GetValue(object obj);
        void SetValue(object obj, object value);

    }

So the loop to copy public Field and Property values to a target object now looks like this:

 public static void Copy(object source, object target, ObjectMapperCopyValidator rules)
{
    Dictionary<string, IGetterSetter> target_properties = ClassUtils.GetGetterSetters(target);
    Dictionary<string, IGetterSetter> source_properties = ClassUtils.GetMatchingGetterSetters(target_properties.Keys, source);

    foreach (var entry in source_properties)
    {
        var sourceProperty = entry.Value;
        var targetProperty = target_properties[entry.Key];
        // for now, match datatypes directly
        if (sourceProperty.DataType == targetProperty.DataType)
        {
            // if property not readable or writeable, skip
            if (!(sourceProperty.CanRead && targetProperty.CanWrite))
            {
                continue;
            }

            var sourceValue = sourceProperty.GetValue(source);
            try
            {
                if (rules.IsValid(sourceProperty, sourceValue))
                {
                    targetProperty.SetValue(target, sourceValue);
                }
            }
            catch (TargetException e)
            {
                LOG.ErrorFormat("unable to set value {0} for property={1}, ex={2}", sourceValue, targetProperty, e);
            }
        }
    }
}

Wrapping PropertyInfo and FieldInfo into the common interface was the simplest part:

   public static Dictionary<string, IGetterSetter> GetGetterSetters(object target)
    {
        return target.GetType().GetMembers().Where(x => x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property).ToDictionary(x => x.Name, x => GetterSetterFactory.Instance.Create(x));
    }

So the ugliness of normalizing and casting PropertyInfo and FieldInfo is hidden in the GetterSetterFactory.Instance.Create(x) method.

I also ran into this problem, but I didn't want to use an interface, because interfaces can come with steep performance penalties in tight loops. Since I'm writing a serializer that needs to be as fast as possible, I went with an extension method solution to MemberInfo.

public static void SetValue(this MemberInfo mi, object targetObject, object value)
{
    switch (mi.MemberType)
    {
        case MemberTypes.Field:
            try
            {
                (mi as FieldInfo).SetValue(targetObject, value);
            }
            catch(Exception e)
            {
                throw new GeneralSerializationException($"Could not set field {mi.Name} on object of type {targetObject.GetType()}.", e);
            }
            break;

        case MemberTypes.Property:
            try
            {
                (mi as PropertyInfo).SetValue(targetObject, value);
            }
            catch(Exception e)
            {
                throw new GeneralSerializationException($"Could not set property {mi.Name} on object of type {targetObject.GetType()}.", e);
            }

            break;
        default:
            throw new GeneralSerializationException($"MemberInfo must be a subtype of FieldInfo or PropertyInfo.");
    }
}

So now you can just call MemberInfo.SetValue(object, value).

You could setup additional extension methods for the other members and methods you need access to.

UPDATE 8.31.2019 : I updated the code to be more robust with more meaningful error reporting.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM