303 lines
14 KiB
C#
303 lines
14 KiB
C#
using System;
|
||||
|
|
using System.Collections;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.ComponentModel;
|
|||
|
|
using System.Globalization;
|
|||
|
|
using System.Text;
|
|||
|
|
using Newtonsoft.Json;
|
|||
|
|
using SGModule.Common.Helper;
|
|||
|
|
|
|||
|
|
namespace SGModule.Common.Extensions {
|
|||
|
|
/// <summary>
|
|||
|
|
/// 提供通用对象类型转换的扩展方法,支持数值类型、字符串、枚举、集合、时间等多种类型间的智能转换。
|
|||
|
|
/// </summary>
|
|||
|
|
public static class ObjectExtensions {
|
|||
|
|
/// <summary>
|
|||
|
|
/// 将对象强制转换为目标类型 <typeparamref name="T" />,转换失败时返回默认值。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <typeparam name="T">目标类型</typeparam>
|
|||
|
|
/// <param name="value">待转换的对象</param>
|
|||
|
|
/// <param name="defaultValue">转换失败时返回的默认值</param>
|
|||
|
|
/// <param name="format">可选格式字符串(如用于时间或数值格式化)</param>
|
|||
|
|
/// <param name="formatProvider">格式化提供者,默认为当前区域设置</param>
|
|||
|
|
/// <returns>转换后的目标类型值,或 <paramref name="defaultValue" />。</returns>
|
|||
|
|
/// <example>
|
|||
|
|
/// 字符串到基本类型
|
|||
|
|
/// <code>
|
|||
|
|
/// "123".As<int>(); // 输出 123
|
|||
|
|
/// "45.67".As<float>(); // 输出 45.67f
|
|||
|
|
/// "true".As<bool>(); // 输出 true
|
|||
|
|
/// "2023-01-01".As<DateTime>(); // 输出 2023-01-01
|
|||
|
|
/// "".As<int>(); // 输出 0
|
|||
|
|
/// ((string)null).As<int>(); // 输出 0
|
|||
|
|
/// "abc".As<int>(); // 输出 0(无法转换)
|
|||
|
|
/// "1.23E+5".As<float>(); // 输出 123000f
|
|||
|
|
/// .....
|
|||
|
|
/// </code>
|
|||
|
|
/// </example>
|
|||
|
|
public static T As<T>(this object value, T defaultValue = default, string format = null, IFormatProvider formatProvider = null) {
|
|||
|
|
return (T) As(value, typeof(T), defaultValue, format, formatProvider);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 将对象转换为指定的目标类型。
|
|||
|
|
/// 支持基础类型转换、字符串与 JSON 互转、枚举解析、集合映射、Base64 处理等。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="value">要转换的对象</param>
|
|||
|
|
/// <param name="targetType">目标类型</param>
|
|||
|
|
/// <param name="defaultValue">转换失败时的返回值</param>
|
|||
|
|
/// <param name="format">可选的格式字符串(用于时间、数字格式化)</param>
|
|||
|
|
/// <param name="formatProvider">格式提供者(区域信息),默认为当前区域</param>
|
|||
|
|
/// <returns>转换结果或默认值</returns>
|
|||
|
|
public static object As(this object value, Type targetType, object defaultValue = null, string format = null, IFormatProvider formatProvider = null) {
|
|||
|
|
if (value == null || value == DBNull.Value) {
|
|||
|
|
return defaultValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
formatProvider ??= CultureInfo.CurrentCulture;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 1. 类型兼容直接返回
|
|||
|
|
if (targetType.IsInstanceOfType(value)) {
|
|||
|
|
return value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 数值类型之间转换(int, float, decimal 等)
|
|||
|
|
if (value.IsNumericType() && targetType.IsNumericType()) {
|
|||
|
|
return Convert.ChangeType(value, targetType, formatProvider);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 字符串 ⇄ 字节数组
|
|||
|
|
if (targetType == typeof(byte[]) && value is string str) {
|
|||
|
|
return Encoding.UTF8.GetBytes(str);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (targetType == typeof(string) && value is byte[] bytes) {
|
|||
|
|
return Encoding.UTF8.GetString(bytes);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 转为字符串
|
|||
|
|
if (targetType == typeof(string)) {
|
|||
|
|
if (value is string s) {
|
|||
|
|
return s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (value.IsNumericType()) {
|
|||
|
|
return string.Format(formatProvider, format ?? "{0}", value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (value is DateTime dt) {
|
|||
|
|
return string.IsNullOrEmpty(format) ? dt.ToString(formatProvider) : dt.ToString(format, formatProvider);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
return JsonConvert.SerializeObject(value);
|
|||
|
|
}
|
|||
|
|
catch {
|
|||
|
|
return defaultValue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. 从字符串解析为目标类型
|
|||
|
|
if (value is string strValue) {
|
|||
|
|
if (string.IsNullOrWhiteSpace(strValue)) {
|
|||
|
|
return defaultValue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 字符串 → 枚举
|
|||
|
|
if (targetType.IsEnum) {
|
|||
|
|
try {
|
|||
|
|
return Enum.Parse(targetType, strValue, true);
|
|||
|
|
}
|
|||
|
|
catch {
|
|||
|
|
return defaultValue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 字符串 → 数值
|
|||
|
|
if (targetType.IsNumericType()) {
|
|||
|
|
return Convert.ChangeType(strValue, targetType, formatProvider);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 字符串 → 时间
|
|||
|
|
if (targetType == typeof(DateTime)) {
|
|||
|
|
return string.IsNullOrEmpty(format)
|
|||
|
|
? DateTime.Parse(strValue, formatProvider)
|
|||
|
|
: DateTime.ParseExact(strValue, format, formatProvider);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 字符串 → 自定义类(JSON)
|
|||
|
|
if (targetType.IsClass && targetType != typeof(string)) {
|
|||
|
|
try {
|
|||
|
|
return JsonConvert.DeserializeObject(strValue, targetType);
|
|||
|
|
}
|
|||
|
|
catch {
|
|||
|
|
return defaultValue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 6. 集合类型转换(如 List<string> -> List<int>)
|
|||
|
|
if (targetType.IsGenericListOrArray() && value is IEnumerable enumerable) {
|
|||
|
|
var elementType = targetType.GetElementType() ?? targetType.GetGenericArguments()[0];
|
|||
|
|
var list = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
|
|||
|
|
|
|||
|
|
foreach (var item in enumerable) {
|
|||
|
|
var converted = typeof(ObjectExtensions)
|
|||
|
|
.GetMethod("As", new[] { typeof(object), typeof(Type), typeof(object), typeof(string), typeof(IFormatProvider) })
|
|||
|
|
?.Invoke(null, new[] { item, elementType, null, format, formatProvider });
|
|||
|
|
|
|||
|
|
list.Add(converted);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return targetType.IsArray ? list.ToArray(elementType) : list;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 7. 尝试通过 TypeConverter 转换(主要处理结构体、基础类型)
|
|||
|
|
if (targetType.IsValueType || targetType == typeof(string)) {
|
|||
|
|
var strVal = value.ToString();
|
|||
|
|
if (!string.IsNullOrWhiteSpace(strVal)) {
|
|||
|
|
var converter = TypeDescriptor.GetConverter(targetType);
|
|||
|
|
if (converter.CanConvertFrom(typeof(string))) {
|
|||
|
|
return converter.ConvertFrom(null, formatProvider as CultureInfo, strVal);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 8. Fallback:通过 JSON 转换
|
|||
|
|
return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType);
|
|||
|
|
}
|
|||
|
|
catch {
|
|||
|
|
return defaultValue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region 🔧 辅助方法
|
|||
|
|
|
|||
|
|
private static bool IsNumericType(this Type type) {
|
|||
|
|
return Type.GetTypeCode(type) switch {
|
|||
|
|
TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 or TypeCode.UInt32 or
|
|||
|
|
TypeCode.UInt64 or TypeCode.Int16 or TypeCode.Int32 or TypeCode.Int64 or
|
|||
|
|
TypeCode.Decimal or TypeCode.Double or TypeCode.Single => true,
|
|||
|
|
_ => false
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static bool IsNumericType(this object value) {
|
|||
|
|
return value?.GetType().IsNumericType() ?? false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static bool IsGenericListOrArray(this Type type) {
|
|||
|
|
return type != null && (type.IsArray ||
|
|||
|
|
(type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>)
|
|||
|
|
|| type.GetGenericTypeDefinition() == typeof(IList<>))));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static Array ToArray(this IList list, Type elementType) {
|
|||
|
|
var array = Array.CreateInstance(elementType, list.Count);
|
|||
|
|
list.CopyTo(array, 0);
|
|||
|
|
return array;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static class ObjectExtensionsTest {
|
|||
|
|
public static void AsTestRun() {
|
|||
|
|
TestCase("int 到 int", 123.As<int>(), 123);
|
|||
|
|
TestCase("float 到 int", 123.1f.As<int>(), 123);
|
|||
|
|
TestCase("double 到 decimal", 99.99.As<decimal>(), 99.99m);
|
|||
|
|
TestCase("int 到 long", 100.As<long>(), 100L);
|
|||
|
|
|
|||
|
|
TestCase("字符串 到 int", "123".As<int>(), 123);
|
|||
|
|
TestCase("字符串 到 float", "45.67".As<float>(), 45.67f);
|
|||
|
|
TestCase("字符串 到 bool", "true".As<bool>(), true);
|
|||
|
|
TestCase("字符串 到 DateTime", "2023-01-01".As<DateTime>(), new DateTime(2023, 1, 1));
|
|||
|
|
TestCase("空字符串 到 int", "".As<int>(), 0);
|
|||
|
|
TestCase("null字符串 到 int", ((string) null).As<int>(), 0);
|
|||
|
|
TestCase("非法字符串 到 int", "abc".As<int>(), 0);
|
|||
|
|
TestCase("科学计数法字符串 到 float", "1.23E+5".As<float>(), 123000f);
|
|||
|
|
|
|||
|
|
TestCase("int 到 字符串", 42.As<string>(), "42");
|
|||
|
|
TestCase("float 到 字符串(en-US)", 3.14159f.As<string>(formatProvider: new CultureInfo("en-US")), "3.14159");
|
|||
|
|
TestCase("float 到 字符串(de-DE)", 3.14159f.As<string>(formatProvider: new CultureInfo("de-DE")), "3,14159");
|
|||
|
|
TestCase("DateTime 到 字符串(格式化)", new DateTime(2024, 5, 1).As<string>(format: "yyyy-MM-dd"), "2024-05-01");
|
|||
|
|
|
|||
|
|
TestCase("null 到 int", ((object) null).As<int>(), 0);
|
|||
|
|
TestCase("null 到 string", ((object) null).As<string>(), null);
|
|||
|
|
TestCase("DBNull 到 int", DBNull.Value.As<int>(), 0);
|
|||
|
|
|
|||
|
|
TestCase("枚举 到 string", Color.Red.As<string>(), "Red");
|
|||
|
|
TestCase("字符串 到 枚举", "Green".As<Color>(), Color.Green);
|
|||
|
|
|
|||
|
|
var json = "{\"Name\":\"Alice\",\"Age\":30}";
|
|||
|
|
TestCase("JSON 字符串 到 Person", json.As<Person>().Name, "Alice");
|
|||
|
|
TestCase("对象 到 JSON 字符串", new Person { Name = "Alice", Age = 30 }.As<string>(), JsonConvert.SerializeObject(new Person { Name = "Alice", Age = 30 }));
|
|||
|
|
|
|||
|
|
TestCase("字符串数组 到 List<int>", new[] { "1", "2", "3" }.As<List<int>>(), new List<int> { 1, 2, 3 });
|
|||
|
|
TestCase("List<float> 到 float[]", new List<float> { 1.1f, 2.2f }.As<float[]>(), new[] { 1.1f, 2.2f });
|
|||
|
|
TestCase("空集合 到 int[]", new string[] { }.As<int[]>(), new int[] { });
|
|||
|
|
|
|||
|
|
var str = "hello";
|
|||
|
|
var bytes = Encoding.UTF8.GetBytes(str);
|
|||
|
|
TestCase("byte[] 到 base64 string", bytes.As<string>(), str);
|
|||
|
|
TestCase("base64 string 到 byte[]", BitConverter.ToString(str.As<byte[]>()), BitConverter.ToString(bytes));
|
|||
|
|
|
|||
|
|
TestCase("字符串小数(德语) 到 float", "1,23".As<float>(formatProvider: new CultureInfo("de-DE")), 1.23f);
|
|||
|
|
TestCase("字符串小数(英语) 到 float", "1.23".As<float>(formatProvider: new CultureInfo("en-US")), 1.23f);
|
|||
|
|
|
|||
|
|
TestCase("非法 JSON 到 Person(失败返回 null)", "{invalid}".As<Person>(), null);
|
|||
|
|
TestCase("不能转的对象 到 int", new Person { Name = "A" }.As<int>(), 0);
|
|||
|
|
TestCase("不能转的对象 到 string(fallback 到 JsonConvert)", new Person { Name = "A", Age = 20 }.As<string>(), JsonConvert.SerializeObject(new Person { Name = "A", Age = 20 }));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static void TestCase<T>(string description, T actual, T expected) {
|
|||
|
|
var isSuccess = CommonUtils.DeepEquals(actual, expected);
|
|||
|
|
|
|||
|
|
var actualStr = FormatValue(actual);
|
|||
|
|
var expectedStr = FormatValue(expected);
|
|||
|
|
var msg = $"{description} => 结果: {actualStr}, 期望: {expectedStr}";
|
|||
|
|
if (isSuccess) {
|
|||
|
|
Log.Info("As<T>()", msg);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
Log.Warning("As<T>()", msg);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static string FormatValue<T>(T value) {
|
|||
|
|
if (value == null) {
|
|||
|
|
return "null";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (value is byte[] byteArray) {
|
|||
|
|
return $"[{string.Join(", ", byteArray)}]";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (value is int[] intArray) {
|
|||
|
|
return $"[{string.Join(", ", intArray)}]";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return value.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private enum Color {
|
|||
|
|
Red,
|
|||
|
|
Green,
|
|||
|
|
Blue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private class Person {
|
|||
|
|
public string Name {
|
|||
|
|
get;
|
|||
|
|
set;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int Age {
|
|||
|
|
get;
|
|||
|
|
set;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|