Files
RedHotRoast-ios/Assets/SGModule/Common/SGModule/Scripts/Extensions/ObjectExtensions.cs
T

303 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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&lt;int&gt;(); // 输出 123
/// "45.67".As&lt;float&gt;(); // 输出 45.67f
/// "true".As&lt;bool&gt;(); // 输出 true
/// "2023-01-01".As&lt;DateTime&gt;(); // 输出 2023-01-01
/// "".As&lt;int&gt;(); // 输出 0
/// ((string)null).As&lt;int&gt;(); // 输出 0
/// "abc".As&lt;int&gt;(); // 输出 0(无法转换)
/// "1.23E+5".As&lt;float&gt;(); // 输出 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("不能转的对象 到 stringfallback 到 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;
}
}
}
}