粘土对象Clay( 附源码)

简介

粘土对象是我从 Furion 框架中扒出来的,是一种可以模拟弱语言特性的对象,类似 Javascript 一样操作对象。只需通过 Clay 类初始化即可。
为什么起名为 “粘土” 呢?因为这个对象可以自由的添加属性,移除属性,又可以转换成任何对象,具有可拓展、可塑造的特点。
粘土性能略输于强类型调用。Clay 对象是继承自 DynamicObject 的一个特殊对象,提供了向弱语言一样操作对象的方法及索引。

使用场景

粘土对象常用于需要动态构建对象的地方,如 CMS 系统的 ViewModel,或者运行时创建一个新的对象,或者请求第三方 API 情况。

如何使用

创建一个对象

// 创建一个空的粘土对象
dynamic clay = new Clay();

// 从现有的对象创建
var clay2 = Clay.Object(new {});

// 从 json 字符串创建,可用于第三方 API 对接,非常有用
var clay3 = Clay.Parse(@"{""foo"":""json"", ""bar"":100, ""nest"":{ ""foobar"":true } }");

29.3.2 读取/获取属性#

var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    }
});

var r1 = clay.Foo; // "json" - string类型
var r2 = clay.Bar; // 100 - double类型
var r3 = clay.Nest.Foobar; // true - bool类型
var r4 = clay["Nest"]["Foobar"]; // 还可以和 Javascript 一样通过索引器获取

29.3.3 新增属性

var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    }
});

// 新增
clay.Arr = new string[] { "NOR", "XOR" }; // 添加一个数组
clay.Obj1 = new City { }; // 新增一个实例对象
clay.Obj2 = new { Foo = "abc", Bar = 100 }; // 新增一个匿名类

更新属性值

var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    }
});

// 更新
clay.Foo = "Furion";
clay["Nest"].Foobar = false;
clay.Nest["Foobar"] = true;

删除属性

var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    },
    Arr = new string[] { "NOR", "XOR" }
});

// 删除操作
clay.Delete("Foo"); // 通过 Delete 方法删除
clay.Arr.Delete(0); // 支持数组 Delete 索引删除
clay("Bar");    // 支持直接通过对象作为方法删除
clay.Arr(1);    // 支持数组作为方法删除

判断属性是否存在

var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    },
    Arr = new string[] { "NOR", "XOR" }
});
// 判断属性是否存在
var a = clay.IsDefined("Foo"); // true
var b = clay.IsDefined("Foooo"); // false
var c = clay.Foo(); // true
var d = clay.Foooo(); // false;

遍历对象

var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    },
    Arr = new string[] { "NOR", "XOR" }
});

// 遍历数组
foreach (string item in clay.Arr)
{
    Console.WriteLine(item); // NOR, XOR
}

// 遍历整个对象属性及值,类似 JavaScript 的 for (var p in obj)
foreach (KeyValuePair<string, dynamic> item in clay)
{
    Console.WriteLine(item.Key + ":" + item.Value); // Foo:json, Bar: 100, Nest: { "Foobar":true}, Arr:["NOR","XOR"]
}

转换成具体对象

dynamic clay = new Clay();
clay.Arr = new string[] { "Furion", "Fur" };

// 数组转换示例
var a1 = clay.Arr.Deserialize<string[]>(); // 通过 Deserialize 方法
var a2 = (string[])clay.Arr;    // 强制转换
string[] a3 = clay.Arr; // 声明方式

// 对象转换示例
clay.City = new City { Id = 1, Name = "中山市" };
var c1 = clay.City.Deserialize<City>(); // 通过 Deserialize 方法
var c2 = (City)clay.City;    // 强制转换
City c3 = clay.City; // 声明方式

固化粘土

固化粘土在很多时候和序列化很像,但是如果直接调用 Deserialize 或 Deserialize 无法返回实际类型,所以就有了固化类型的功能,如:

// 返回 object
var obj = clay.Solidify();

// 返回 dynamic
var obj1 = clay.Solidify<dynamic>();

// 返回其他任意类型
var obj2 = clay.Solidify<City>();
29.3.10 输出 JSON#
var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    },
    Arr = new string[] { "NOR", "XOR" }
});

输出 JSON

var json = clay.ToString(); // "{\"Foo\":\"json\",\"Bar\":100,\"Nest\":{\"Foobar\":true},\"Arr\":[\"NOR\",\"XOR\"]}"
29.3.11 输出 XML 对象#
var clay = Clay.Object(new
{
    Foo = "json",
    Bar = 100,
    Nest = new
    {
        Foobar = true
    },
    Arr = new string[] { "NOR", "XOR" }
});

关键字处理

dynamic clay = new Clay();
clay.@int = 1;
clay.@event = "事件";

源码

Clay

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;

namespace ClayObject
{
    /// <summary>
    /// 粘土对象
    /// </summary>
    public class Clay : DynamicObject
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public Clay()
        {
            XmlElement = new XElement("root", CreateTypeAttr(JsonType.@object));
            jsonType = JsonType.@object;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="element"></param>
        /// <param name="type"></param>
        private Clay(XElement element, JsonType type)
        {
            Debug.Assert(type == JsonType.array || type == JsonType.@object);

            XmlElement = element;
            jsonType = type;
        }

        /// <summary>
        /// 是否是 Object 类型
        /// </summary>
        public bool IsObject => jsonType == JsonType.@object;

        /// <summary>
        /// 是否是 Array 类型
        /// </summary>
        public bool IsArray => jsonType == JsonType.array;

        /// <summary>
        /// XML 元素
        /// </summary>
        public XElement XmlElement { get; private set; }

        /// <summary>
        /// 创建一个超级类型
        /// </summary>
        /// <returns></returns>
        public static dynamic Object()
        {
            return new Clay();
        }

        /// <summary>
        /// 基于现有类型创建一个超级类型
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static dynamic Object(object obj)
        {
            return Parse(Serialize(obj));
        }

        /// <summary>
        /// 将 Json 转换成动态类型
        /// </summary>
        /// <param name="json"></param>
        /// <returns></returns>
        public static dynamic Parse(string json)
        {
            return Parse(json, Encoding.Unicode);
        }

        /// <summary>
        /// 将 Json 转换成动态类型
        /// </summary>
        /// <param name="json"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static dynamic Parse(string json, Encoding encoding)
        {
            using var reader = JsonReaderWriterFactory.CreateJsonReader(encoding.GetBytes(json), XmlDictionaryReaderQuotas.Max);
            return ToValue(XElement.Load(reader));
        }

        /// <summary>
        /// 将 Steam 转换成动态类型
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static dynamic Parse(Stream stream)
        {
            using var reader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max);
            return ToValue(XElement.Load(reader));
        }

        /// <summary>
        /// 将 Steam 转换成动态类型
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static dynamic Parse(Stream stream, Encoding encoding)
        {
            using var reader = JsonReaderWriterFactory.CreateJsonReader(stream, encoding, XmlDictionaryReaderQuotas.Max, _ => { });
            return ToValue(XElement.Load(reader));
        }

        /// <summary>
        /// 序列化对象
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string Serialize(object obj)
        {
            return CreateJsonString(new XStreamingElement("root", CreateTypeAttr(GetJsonType(obj)), CreateJsonNode(obj)));
        }

        /// <summary>
        /// 是否定义某个键
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool IsDefined(string name)
        {
            return IsObject && (XmlElement.Element(name) != null);
        }

        /// <summary>
        /// 判断数组索引是否存在
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool IsDefined(int index)
        {
            return IsArray && (XmlElement.Elements().ElementAtOrDefault(index) != null);
        }

        /// <summary>
        /// 删除键
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool Delete(string name)
        {
            var elem = XmlElement.Element(name);
            if (elem != null)
            {
                elem.Remove();
                return true;
            }
            else return false;
        }

        /// <summary>
        /// 根据索引删除元素
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool Delete(int index)
        {
            var elem = XmlElement.Elements().ElementAtOrDefault(index);
            if (elem != null)
            {
                elem.Remove();
                return true;
            }
            else return false;
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T Deserialize<T>()
        {
            return (T)Deserialize(typeof(T));
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
            result = (IsArray)
                ? Delete((int)args[0])
                : Delete((string)args[0]);
            return true;
        }

        /// <summary>
        /// 判断是否定义
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            if (args.Length > 0)
            {
                result = null;
                return false;
            }

            result = IsDefined(binder.Name);
            return true;
        }

        /// <summary>
        /// 支持 Foreach 遍历
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            if (binder.Type == typeof(IEnumerable) || binder.Type == typeof(object[]))
            {
                var ie = (IsArray)
                    ? XmlElement.Elements().Select(x => ToValue(x))
                    : XmlElement.Elements().Select(x => (dynamic)new KeyValuePair<string, object>(x.Name.LocalName, ToValue(x)));
                result = (binder.Type == typeof(object[])) ? ie.ToArray() : ie;
            }
            else
            {
                result = Deserialize(binder.Type);
            }
            return true;
        }

        /// <summary>
        /// 获取索引值
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="indexes"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            return (IsArray)
                ? TryGet(XmlElement.Elements().ElementAtOrDefault((int)indexes[0]), out result)
                : TryGet(XmlElement.Element((string)indexes[0]), out result);
        }

        /// <summary>
        /// 获取成员值
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return (IsArray)
                ? TryGet(XmlElement.Elements().ElementAtOrDefault(int.Parse(binder.Name)), out result)
                : TryGet(XmlElement.Element(binder.Name), out result);
        }

        /// <summary>
        /// 设置索引
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="indexes"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
            return (IsArray)
                ? TrySet((int)indexes[0], value)
                : TrySet((string)indexes[0], value);
        }

        /// <summary>
        /// 设置成员
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            return (IsArray)
                ? TrySet(int.Parse(binder.Name), value)
                : TrySet(binder.Name, value);
        }

        /// <summary>
        /// 获取动态成员名称
        /// </summary>
        /// <returns></returns>
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return (IsArray)
                ? XmlElement.Elements().Select((x, i) => i.ToString())
                : XmlElement.Elements().Select(x => x.Name.LocalName);
        }

        /// <summary>
        /// 重写 .ToString()
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            // <foo type="null"></foo> is can't serialize. replace to <foo type="null" />
            foreach (var elem in XmlElement.Descendants().Where(x => x.Attribute("type").Value == "null"))
            {
                elem.RemoveNodes();
            }
            return CreateJsonString(new XStreamingElement("root", CreateTypeAttr(jsonType), XmlElement.Elements()));
        }

        /// <summary>
        /// 固化粘土,也就是直接输出对象
        /// </summary>
        /// <returns></returns>
        public object Solidify()
        {
            return Solidify<object>();
        }

        /// <summary>
        /// 固化粘土,也就是直接输出对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T Solidify<T>()
        {
            return System.Text.Json.JsonSerializer.Deserialize<T>(ToString());
            //return JSON.Deserialize<T>(ToString());
        }

        /// <summary>
        /// JSON 类型
        /// </summary>
        private enum JsonType
        {
            @string, number, boolean, @object, array, @null
        }

        /// <summary>
        /// XElement 转动态类型
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private static dynamic ToValue(XElement element)
        {
            var type = (JsonType)Enum.Parse(typeof(JsonType), element.Attribute("type").Value);
            return type switch
            {
                JsonType.boolean => (bool)element,
                JsonType.number => (double)element,
                JsonType.@string => (string)element,
                JsonType.@object or JsonType.array => new Clay(element, type),
                _ => null,
            };
        }

        /// <summary>
        /// 获取 JSON 类型
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static JsonType GetJsonType(object obj)
        {
            if (obj == null) return JsonType.@null;

            return Type.GetTypeCode(obj.GetType()) switch
            {
                TypeCode.Boolean => JsonType.boolean,
                TypeCode.String or TypeCode.Char or TypeCode.DateTime => JsonType.@string,
                TypeCode.Int16 or TypeCode.Int32 or TypeCode.Int64 or TypeCode.UInt16 or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Single or TypeCode.Double or TypeCode.Decimal or TypeCode.SByte or TypeCode.Byte => JsonType.number,
                TypeCode.Object => (obj is IEnumerable) ? JsonType.array : JsonType.@object,
                _ => JsonType.@null,
            };
        }

        /// <summary>
        /// 创建类型属性
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private static XAttribute CreateTypeAttr(JsonType type)
        {
            return new XAttribute("type", type.ToString());
        }

        /// <summary>
        /// 创建 JSON 节点
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static object CreateJsonNode(object obj)
        {
            var type = GetJsonType(obj);
            return type switch
            {
                JsonType.@string or JsonType.number => obj,
                JsonType.boolean => obj.ToString().ToLower(),
                JsonType.@object => CreateXObject(obj),
                JsonType.array => CreateXArray(obj as IEnumerable),
                _ => null,
            };
        }

        /// <summary>
        /// 创建 XStreamingElement 对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static IEnumerable<XStreamingElement> CreateXArray<T>(T obj) where T : IEnumerable
        {
            return obj.Cast<object>()
                .Select(o => new XStreamingElement("item", CreateTypeAttr(GetJsonType(o)), CreateJsonNode(o)));
        }

        /// <summary>
        /// 创建 XStreamingElement 对象
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static IEnumerable<XStreamingElement> CreateXObject(object obj)
        {
            return obj.GetType()
                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Select(pi => new { pi.Name, Value = pi.GetValue(obj, null) })
                .Select(a => new XStreamingElement(a.Name, CreateTypeAttr(GetJsonType(a.Value)), CreateJsonNode(a.Value)));
        }

        /// <summary>
        /// 创建 JSON 字符串
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private static string CreateJsonString(XStreamingElement element)
        {
            using var ms = new MemoryStream();
            using var writer = JsonReaderWriterFactory.CreateJsonWriter(ms, Encoding.Unicode);
            element.WriteTo(writer);
            writer.Flush();
            return Encoding.Unicode.GetString(ms.ToArray());
        }

        /// <summary>
        /// JSON 类型
        /// </summary>
        private readonly JsonType jsonType;

        /// <summary>
        /// 读取值
        /// </summary>
        /// <param name="element"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        private static bool TryGet(XElement element, out object result)
        {
            if (element == null)
            {
                result = null;
                return false;
            }

            result = ToValue(element);
            return true;
        }

        /// <summary>
        /// 设置值
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private bool TrySet(string name, object value)
        {
            var type = GetJsonType(value);
            var element = XmlElement.Element(name);
            if (element == null)
            {
                XmlElement.Add(new XElement(name, CreateTypeAttr(type), CreateJsonNode(value)));
            }
            else
            {
                element.Attribute("type").Value = type.ToString();
                element.ReplaceNodes(CreateJsonNode(value));
            }

            return true;
        }

        /// <summary>
        /// 设置值
        /// </summary>
        /// <param name="index"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private bool TrySet(int index, object value)
        {
            var type = GetJsonType(value);
            var e = XmlElement.Elements().ElementAtOrDefault(index);
            if (e == null)
            {
                XmlElement.Add(new XElement("item", CreateTypeAttr(type), CreateJsonNode(value)));
            }
            else
            {
                e.Attribute("type").Value = type.ToString();
                e.ReplaceNodes(CreateJsonNode(value));
            }

            return true;
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private object Deserialize(Type type)
        {
            return (IsArray) ? DeserializeArray(type) : DeserializeObject(type);
        }

        /// <summary>
        /// 反序列化值
        /// </summary>
        /// <param name="element"></param>
        /// <param name="elementType"></param>
        /// <returns></returns>
        private static dynamic DeserializeValue(XElement element, Type elementType)
        {
            var value = ToValue(element);
            if (value is Clay json)
            {
                value = json.Deserialize(elementType);
            }

            return ObjectExtensions.ChangeType(value, elementType);
        }

        /// <summary>
        /// 反序列化对象
        /// </summary>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private object DeserializeObject(Type targetType)
        {
            var result = Activator.CreateInstance(targetType);
            var dict = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => p.CanWrite)
                .ToDictionary(pi => pi.Name, pi => pi);

            foreach (var item in XmlElement.Elements())
            {
                if (!dict.TryGetValue(item.Name.LocalName, out var propertyInfo)) continue;
                var value = Clay.DeserializeValue(item, propertyInfo.PropertyType);
                propertyInfo.SetValue(result, value, null);
            }
            return result;
        }

        /// <summary>
        /// 序列化数组
        /// </summary>
        /// <param name="targetType"></param>
        /// <returns></returns>
        private object DeserializeArray(Type targetType)
        {
            if (targetType.IsArray)
            {
                var elemType = targetType.GetElementType();
                dynamic array = Array.CreateInstance(elemType, XmlElement.Elements().Count());
                var index = 0;
                foreach (var item in XmlElement.Elements())
                {
                    array[index++] = Clay.DeserializeValue(item, elemType);
                }
                return array;
            }
            else
            {
                var elemType = targetType.GetGenericArguments()[0];
                dynamic list = Activator.CreateInstance(targetType);
                foreach (var item in XmlElement.Elements())
                {
                    list.Add(Clay.DeserializeValue(item, elemType));
                }
                return list;
            }
        }
    }

}

DictionaryExtensions

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ClayObject.Extensions
{
    /// <summary>
    /// 字典类型拓展类
    /// </summary>
    public static class DictionaryExtensions
    {
        /// <summary>
        /// 将对象转成字典
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static IDictionary<string, object> ToDictionary(this object input)
        {
            if (input == null)
                throw new ArgumentNullException(nameof(input));

            if (input is IDictionary<string, object> dictionary)
                return dictionary;

            var properties = input.GetType().GetProperties();
            var fields = input.GetType().GetFields();
            var members = properties.Cast<MemberInfo>().Concat(fields.Cast<MemberInfo>());

            return members.ToDictionary(m => m.Name, m => GetValue(input, m));
        }

        /// <summary>
        /// 将对象转字典类型,其中值返回原始类型 Type 类型
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static IDictionary<string, Tuple<Type, object>> ToDictionaryWithType(this object input)
        {
            if (input == null)
                throw new ArgumentNullException(nameof(input));

            if (input is IDictionary<string, object> dictionary)
                return dictionary.ToDictionary(
                    kvp => kvp.Key,
                    kvp => kvp.Value == null ?
                        new Tuple<Type, object>(typeof(object), kvp.Value) :
                        new Tuple<Type, object>(kvp.Value.GetType(), kvp.Value)
                );

            var dict = new Dictionary<string, Tuple<Type, object>>();

            // 获取所有属性列表
            foreach (var property in input.GetType().GetProperties())
            {
                dict.Add(property.Name, new Tuple<Type, object>(property.PropertyType, property.GetValue(input, null)));
            }

            // 获取所有成员列表
            foreach (var field in input.GetType().GetFields())
            {
                dict.Add(field.Name, new Tuple<Type, object>(field.FieldType, field.GetValue(input)));
            }

            return dict;
        }

        /// <summary>
        /// 获取成员值
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="member"></param>
        /// <returns></returns>
        private static object GetValue(object obj, MemberInfo member)
        {
            if (member is PropertyInfo info)
                return info.GetValue(obj, null);

            if (member is FieldInfo info1)
                return info1.GetValue(obj);

            throw new ArgumentException("Passed member is neither a PropertyInfo nor a FieldInfo.");
        }
    }
}

ExpandoObjectExtensions

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;

namespace ClayObject.Extensions
{
    /// <summary>
    /// ExpandoObject 对象拓展
    /// </summary>
    public static class ExpandoObjectExtensions
    {
        /// <summary>
        /// 将对象转 ExpandoObject 类型
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static ExpandoObject ToExpandoObject(this object value)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));

            if (value is not ExpandoObject expando)
            {
                expando = new ExpandoObject();
                var dict = (IDictionary<string, object>)expando;

                var dictionary = value.ToDictionary();
                foreach (var kvp in dictionary)
                {
                    dict.Add(kvp);
                }
            }

            return expando;
        }

        /// <summary>
        /// 移除 ExpandoObject 对象属性
        /// </summary>
        /// <param name="expandoObject"></param>
        /// <param name="propertyName"></param>
        public static void RemoveProperty(this ExpandoObject expandoObject, string propertyName)
        {
            if (expandoObject == null)
                throw new ArgumentNullException(nameof(expandoObject));

            if (propertyName == null)
                throw new ArgumentNullException(nameof(propertyName));

            ((IDictionary<string, object>)expandoObject).Remove(propertyName);
        }

        /// <summary>
        /// 判断 ExpandoObject 是否为空
        /// </summary>
        /// <param name="expandoObject"></param>
        /// <returns></returns>
        public static bool Empty(this ExpandoObject expandoObject)
        {
            return !((IDictionary<string, object>)expandoObject).Any();
        }

        /// <summary>
        /// 判断 ExpandoObject 是否拥有某属性
        /// </summary>
        /// <param name="expandoObject"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static bool HasProperty(this ExpandoObject expandoObject, string propertyName)
        {
            if (expandoObject == null)
                throw new ArgumentNullException(nameof(expandoObject));

            if (propertyName == null)
                throw new ArgumentNullException(nameof(propertyName));

            return ((IDictionary<string, object>)expandoObject).ContainsKey(propertyName);
        }

        /// <summary>
        /// 实现 ExpandoObject 浅拷贝
        /// </summary>
        /// <param name="expandoObject"></param>
        /// <returns></returns>
        public static ExpandoObject ShallowCopy(this ExpandoObject expandoObject)
        {
            return Copy(expandoObject, false);
        }

        /// <summary>
        /// 实现 ExpandoObject 深度拷贝
        /// </summary>
        /// <param name="expandoObject"></param>
        /// <returns></returns>
        public static ExpandoObject DeepCopy(this ExpandoObject expandoObject)
        {
            return Copy(expandoObject, true);
        }

        /// <summary>
        /// 拷贝 ExpandoObject 对象
        /// </summary>
        /// <param name="original"></param>
        /// <param name="deep"></param>
        /// <returns></returns>
        private static ExpandoObject Copy(ExpandoObject original, bool deep)
        {
            var clone = new ExpandoObject();

            var _original = (IDictionary<string, object>)original;
            var _clone = (IDictionary<string, object>)clone;

            foreach (var kvp in _original)
            {
                _clone.Add(
                    kvp.Key,
                    deep && kvp.Value is ExpandoObject eObject ? DeepCopy(eObject) : kvp.Value
                );
            }

            return clone;
        }
    }
}

ObjectExtensions

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace ClayObject.Extensions
{
    /// <summary>
    /// 对象拓展类
    /// </summary>
    public static class ObjectExtensions
    {
        /// <summary>
        /// 将 DateTimeOffset 转换成 DateTime
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static DateTime ConvertToDateTime(this DateTimeOffset dateTime)
        {
            if (dateTime.Offset.Equals(TimeSpan.Zero))
                return dateTime.UtcDateTime;
            else if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime)))
                return DateTime.SpecifyKind(dateTime.DateTime, DateTimeKind.Local);
            else
                return dateTime.DateTime;
        }

        /// <summary>
        /// 将 DateTime 转换成 DateTimeOffset
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static DateTimeOffset ConvertToDateTimeOffset(this DateTime dateTime)
        {
            return DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
        }

        /// <summary>
        /// 判断是否是富基元类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        internal static bool IsRichPrimitive(this Type type)
        {
            // 处理元组类型
            if (type.IsValueTuple()) return false;

            // 处理数组类型,基元数组类型也可以是基元类型
            if (type.IsArray) return type.GetElementType().IsRichPrimitive();

            // 基元类型或值类型或字符串类型
            if (type.IsPrimitive || type.IsValueType || type == typeof(string)) return true;

            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) return type.GenericTypeArguments[0].IsRichPrimitive();

            return false;
        }

        /// <summary>
        /// 合并两个字典
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dic">字典</param>
        /// <param name="newDic">新字典</param>
        /// <returns></returns>
        internal static Dictionary<string, T> AddOrUpdate<T>(this Dictionary<string, T> dic, Dictionary<string, T> newDic)
        {
            foreach (var key in newDic.Keys)
            {
                if (dic.ContainsKey(key))
                    dic[key] = newDic[key];
                else
                    dic.Add(key, newDic[key]);
            }

            return dic;
        }

        /// <summary>
        /// 判断是否是元组类型
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        internal static bool IsValueTuple(this Type type)
        {
            return type.ToString().StartsWith(typeof(ValueTuple).FullName);
        }

        /// <summary>
        /// 判断方法是否是异步
        /// </summary>
        /// <param name="method">方法</param>
        /// <returns></returns>
        internal static bool IsAsync(this MethodInfo method)
        {
            return method.ReturnType.IsAsync();
        }

        /// <summary>
        /// 判断类型是否是异步类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal static bool IsAsync(this Type type)
        {
            return type.ToString().StartsWith(typeof(Task).FullName);
        }

        /// <summary>
        /// 判断类型是否实现某个泛型
        /// </summary>
        /// <param name="type">类型</param>
        /// <param name="generic">泛型类型</param>
        /// <returns>bool</returns>
        internal static bool HasImplementedRawGeneric(this Type type, Type generic)
        {
            // 检查接口类型
            var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType);
            if (isTheRawGenericType) return true;

            // 检查类型
            while (type != null && type != typeof(object))
            {
                isTheRawGenericType = IsTheRawGenericType(type);
                if (isTheRawGenericType) return true;
                type = type.BaseType;
            }

            return false;

            // 判断逻辑
            bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type);
        }

        /// <summary>
        /// 判断是否是匿名类型
        /// </summary>
        /// <param name="obj">对象</param>
        /// <returns></returns>
        internal static bool IsAnonymous(this object obj)
        {
            var type = obj.GetType();

            return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
                   && type.IsGenericType && type.Name.Contains("AnonymousType")
                   && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
                   && type.Attributes.HasFlag(TypeAttributes.NotPublic);
        }

        /// <summary>
        /// 获取所有祖先类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal static IEnumerable<Type> GetAncestorTypes(this Type type)
        {
            var ancestorTypes = new List<Type>();
            while (type != null && type != typeof(object))
            {
                if (IsNoObjectBaseType(type))
                {
                    var baseType = type.BaseType;
                    ancestorTypes.Add(baseType);
                    type = baseType;
                }
                else break;
            }

            return ancestorTypes;

            static bool IsNoObjectBaseType(Type type) => type.BaseType != typeof(object);
        }

        /// <summary>
        /// 获取方法真实返回类型
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        internal static Type GetRealReturnType(this MethodInfo method)
        {
            // 判断是否是异步方法
            var isAsyncMethod = method.IsAsync();

            // 获取类型返回值并处理 Task 和 Task<T> 类型返回值
            var returnType = method.ReturnType;
            return isAsyncMethod ? (returnType.GenericTypeArguments.FirstOrDefault() ?? typeof(void)) : returnType;
        }

        /// <summary>
        /// 首字母大写
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        internal static string ToTitleCase(this string str)
        {
            return Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(str);
        }

        /// <summary>
        /// 将一个对象转换为指定类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        internal static T ChangeType<T>(this object obj)
        {
            return (T)ChangeType(obj, typeof(T));
        }

        /// <summary>
        /// 将一个对象转换为指定类型
        /// </summary>
        /// <param name="obj">待转换的对象</param>
        /// <param name="type">目标类型</param>
        /// <returns>转换后的对象</returns>
        internal static object ChangeType(this object obj, Type type)
        {
            if (type == null) return obj;
            if (obj == null) return type.IsValueType ? Activator.CreateInstance(type) : null;

            var underlyingType = Nullable.GetUnderlyingType(type);
            if (type.IsAssignableFrom(obj.GetType())) return obj;
            else if ((underlyingType ?? type).IsEnum)
            {
                if (underlyingType != null && string.IsNullOrWhiteSpace(obj.ToString())) return null;
                else return Enum.Parse(underlyingType ?? type, obj.ToString());
            }
            // 处理DateTime -> DateTimeOffset 类型
            else if (obj.GetType().Equals(typeof(DateTime)) && (underlyingType ?? type).Equals(typeof(DateTimeOffset)))
            {
                return ((DateTime)obj).ConvertToDateTimeOffset();
            }
            // 处理 DateTimeOffset -> DateTime 类型
            else if (obj.GetType().Equals(typeof(DateTimeOffset)) && (underlyingType ?? type).Equals(typeof(DateTime)))
            {
                return ((DateTimeOffset)obj).ConvertToDateTime();
            }
            else if (typeof(IConvertible).IsAssignableFrom(underlyingType ?? type))
            {
                try
                {
                    return Convert.ChangeType(obj, underlyingType ?? type, null);
                }
                catch
                {
                    return underlyingType == null ? Activator.CreateInstance(type) : null;
                }
            }
            else
            {
                var converter = TypeDescriptor.GetConverter(type);
                if (converter.CanConvertFrom(obj.GetType())) return converter.ConvertFrom(obj);

                var constructor = type.GetConstructor(Type.EmptyTypes);
                if (constructor != null)
                {
                    var o = constructor.Invoke(null);
                    var propertys = type.GetProperties();
                    var oldType = obj.GetType();

                    foreach (var property in propertys)
                    {
                        var p = oldType.GetProperty(property.Name);
                        if (property.CanWrite && p != null && p.CanRead)
                        {
                            property.SetValue(o, ChangeType(p.GetValue(obj, null), property.PropertyType), null);
                        }
                    }
                    return o;
                }
            }
            return obj;
        }
    }
}

posted @ 2020-02-21 14:59  .Neterr  阅读(1461)  评论(0编辑  收藏  举报