【问题标题】:C# Custom DynamicObject cast to derived objectC# 自定义 DynamicObject 强制转换为派生对象
【发布时间】:2021-11-01 11:55:40
【问题描述】:

我有一个类 DocumentObject,它扩展 DynamicObject 以允许动态成员属性。

public class DocumentObject : DynamicObject
    {
        /// <summary>
        /// Inner dictionary that holds the dynamic members of the object
        /// </summary>
        Dictionary<string, object> dictionary = new Dictionary<string, object>();

        /// <summary>
        /// Try to get the member that is not defined in the class (additional dynamic members) from inner dictionary
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            string name = binder.Name.ToLower();

            // If the property name is found in a dictionary,
            // set the result parameter to the property value and return true.
            // Otherwise, return false.
            return dictionary.TryGetValue(name, out result);
        }

        /// <summary>
        /// Try to set the member that is not defined in the class (additional dynamic members) to inner dictionary
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            dictionary[binder.Name.ToLower()] = value;

            // You can always add a value to a dictionary,
            // so this method always returns true.
            return true;
        }

        /// <summary>
        /// Get the names of all the dynamic members
        /// </summary>
        /// <returns></returns>
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return dictionary.Keys;
        }

    }

我有一个继承 DocumentObject 的基本 Person 类

public class PersonDto : DocumentObject
    {
        [JsonProperty("id")]
        public string Id { get; set; }
    }

另一个继承 PersonDto 的子 OfficePersonDto 类

public class OfficePersonDto : PersonDto 
    {
        [JsonProperty("name")]
        public string Name { get; set; }
    }

在我的函数中,我收到的 JSON 对象必须至少是 PersonDto 对象,但如果它是 OfficePersonDto 类型,我希望能够将 PersonDto 转换为 OfficePersonDto。 IE。 JSON = {"Id":1, "Name": "Orchard"},在 PersonDto 中,name 属性将使用 DocumentObject 的字典保存,而在转换为 OfficePersonDto 时,Idname 都是该类的属性。

如何从 PersonDto 转换为子类,例如OfficePersonDto?

PersonDto personDto = ...
OfficePersonDto off = personDto as OfficePersonDto  // results in null or Name is null

【问题讨论】:

    标签: c# dynamicobject


    【解决方案1】:

    Automapper 在此类转换问题上非常有用。 我将向您展示一个快速工作的示例。但它仍然会开放,当然可以进行更多重构。

    PersonDto personDto = ...
    // Do not use the following conversion, instead get help from automapper
    // OfficePersonDto off = personDto as OfficePersonDto
        
    var config = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<PersonDto, OfficePersonDto>()
            .ForMember(d => d.Name, opt => opt.MapFrom(new OfficePersonNameResolver()));
    });
        
    var mapper = config.CreateMapper();
    var off = mapper.Map<OfficePersonDto>(personDto); // off is created with correct values
    

    OfficePersonNameResolver 是这样的:

    public class OfficePersonNameResolver : IValueResolver<PersonDto, OfficePersonDto, string>
    {
        public string Resolve(PersonDto source, OfficePersonDto destination, string destMember, ResolutionContext context)
        {
            return (source as dynamic).Name;
        }
    }
    

    如果您对此有任何疑问,欢迎提问。

    编辑:将解析器泛化为IValueResolver

    像这样更改配置的创建:

    var config = new MapperConfiguration(cfg =>
    {
          cfg.CreateMap<PersonDto, OfficePersonDto>()
              .ForMember(d => d.Name, opt => opt.MapFrom(new DynamicObjectValueResolver<PersonDto, OfficePersonDto, string>("name")));
    });
    

    而值解析器现在应该是这样的:

    public class DynamicObjectValueResolver<TSource, TDestination, TDestinationMember> : IValueResolver<TSource, TDestination, TDestinationMember>
       where TDestinationMember : class
    {
        private readonly string _propertyName;
    
        public DynamicObjectValueResolver(string propertyName)
        {
           _propertyName = propertyName;
        }
    
        public TDestinationMember Resolve(TSource source, TDestination destination, TDestinationMember destMember, ResolutionContext context)
        {
            dynamic eo = JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(source));
            IDictionary<string, object> dictionary = eo;
            return dictionary[_propertyName] as TDestinationMember;
         }
    }
    

    工作示例:https://dotnetfiddle.net/KS91To

    【讨论】:

    • 谢谢@Koray Elbek,请问是否可以将解析器泛化为 IValueResolver,其中 S 和 D 属于 DocumentObject 类型?这样我就不必为 DocumentObject 的每个基类创建解析器。
    • 看到您的更新,如果我也可以概括映射器配置,是否有可能? cfg.CreateMap()?我试过了。但是从 DocumentObject 到 OfficePersonDto 获取 invalidCastException。
    • 老实说,我认为配置的泛化也是不可能的。即使有可能,也可能需要大量的脚手架。这将导致严重的过度设计。
    猜你喜欢
    • 2011-07-15
    • 2011-06-23
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-10
    • 2020-09-01
    相关资源
    最近更新 更多