繁体   English   中英

如何使用C#Reflection使用通用代码设置属性和字段?

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

我的代码想要迭代包含类型的FieldInfo和PropertyInfo的Dictionary,并使用该Dictionary将值从一个对象映射到另一个。 例如:

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

上面的问题是:1) dataTypesMatch()函数需要2个不同的方法签名,一个用于FieldInfo ,一个用于PropertyInfo (然后检查每个的类型并进行适当的转换以分派到正确的函数)。 这是因为检查Field数据类型使用FieldInfo.FieldType而Property的数据类型使用PropertyInfo.PropertyType

2)即使FieldInfoPropertyInfo都具有SetValueGetValue方法,它们也不是从公共父类派生的,因此它再次需要强制转换。 (也许Dynamic会解决这个问题?)

是否存在一种解决方案,允许一般性地处理这两种类型的MemberInfo对象以检查DataType和Get / SetValue?

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

由于似乎没有任何本机解决方案,因此我将PropertyInfoFieldInfo对象包装在一个接口中,该接口使客户端代码可以使用它们的相关属性和方法,而不必在代码主体中进行分支和强制转换。

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

    }

因此,将公共Field和Property值复制到目标对象的循环现在看起来像这样:

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

将PropertyInfo和FieldInfo包装到公共接口是最简单的部分:

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

因此,在GetterSetterFactory.Instance.Create(x)方法中隐藏了规范化和强制转换PropertyInfoFieldInfoGetterSetterFactory.Instance.Create(x)

我也遇到了这个问题,但是我不想使用接口,因为接口在紧密的循环中会带来严重的性能损失。 由于我正在编写需要尽可能快的序列化程序,因此我使用了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.");
    }
}

因此,现在您只需调用MemberInfo.SetValue(object,value)。

您可以为其他成员和需要访问的方法设置其他扩展方法。

更新8.31.2019 :我将代码更新为更健壮,并提供了更有意义的错误报告。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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