Explorar o código

ref: 升级到 C# 语言版本 12

龙腾猫跃 hai 6 meses
pai
achega
e8b7ac33e0

+ 150 - 156
Extensions/EnumerableExtensions.cs

@@ -4,182 +4,176 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 
-namespace MeloongCore.Extensions {
-    public static class EnumerableExtensions {
+namespace MeloongCore.Extensions;
+public static class EnumerableExtensions {
 
-        #region Distinct
+    #region Distinct
 
-        /// <summary>
-        /// 通过比较器进行去重。
-        /// 该方法的效率较低,建议仅在小型列表上使用,或换用 DistinctBy。
-        /// </summary>
-        public static IEnumerable<T> Distinct<T>(this IList<T> list, Func<T, T, bool> comparer) {
-            for (int i = 0; i < list.Count; i++) {
-                if (!list.Skip(i + 1).Any(item => comparer(list[i], item))) yield return list[i];
-            }
+    /// <summary>
+    /// 通过比较器进行去重。
+    /// 该方法的效率较低,建议仅在小型列表上使用,或换用 DistinctBy。
+    /// </summary>
+    public static IEnumerable<T> Distinct<T>(this IList<T> list, Func<T, T, bool> comparer) {
+        for (int i = 0; i < list.Count; i++) {
+            if (!list.Skip(i + 1).Any(item => comparer(list[i], item))) yield return list[i];
         }
-        /// <summary>
-        /// 按对象的指定值去重。
-        /// </summary>
-        public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector) {
-            var seen = new HashSet<TKey>();
-            foreach (var element in source) {
-                if (seen.Add(selector(element))) yield return element;
-            }
+    }
+    /// <summary>
+    /// 按对象的指定值去重。
+    /// </summary>
+    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector) {
+        var seen = new HashSet<TKey>();
+        foreach (var element in source) {
+            if (seen.Add(selector(element))) yield return element;
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region MinBy / MaxBy
+    #region MinBy / MaxBy
 
-        /// <summary>
-        /// 选择所有最大值对应的对象。
-        /// 若没有元素则返回空列表。
-        /// </summary>
-        public static List<T> MaxByAll<T, C>(this IEnumerable<T> source, Func<T, C> selector) where C : IComparable<C> {
-            var results = new List<T>();
-            using (var enumerator = source.GetEnumerator()) {
-                if (!enumerator.MoveNext()) return results;
-                T maxItem = enumerator.Current;
-                C maxValue = selector(maxItem);
-                results.Add(maxItem);
-                while (enumerator.MoveNext()) {
-                    T currentItem = enumerator.Current;
-                    C currentValue = selector(currentItem);
-                    int comparisonResult = currentValue.CompareTo(maxValue);
-                    if (comparisonResult > 0) {
-                        maxValue = currentValue;
-                        results.Clear();
-                        results.Add(currentItem);
-                    } else if (comparisonResult == 0) {
-                        results.Add(currentItem);
-                    }
-                }
+    /// <summary>
+    /// 选择所有最大值对应的对象。
+    /// 若没有元素则返回空列表。
+    /// </summary>
+    public static List<T> MaxByAll<T, C>(this IEnumerable<T> source, Func<T, C> selector) where C : IComparable<C> {
+        var results = new List<T>();
+        using var enumerator = source.GetEnumerator();
+        if (!enumerator.MoveNext()) return results;
+        T maxItem = enumerator.Current;
+        C maxValue = selector(maxItem);
+        results.Add(maxItem);
+        while (enumerator.MoveNext()) {
+            T currentItem = enumerator.Current;
+            C currentValue = selector(currentItem);
+            int comparisonResult = currentValue.CompareTo(maxValue);
+            if (comparisonResult > 0) {
+                maxValue = currentValue;
+                results.Clear();
+                results.Add(currentItem);
+            } else if (comparisonResult == 0) {
+                results.Add(currentItem);
             }
-            return results;
         }
-        /// <summary>
-        /// 选择所有最小值对应的对象。
-        /// 若没有元素则返回空列表。
-        /// </summary>
-        public static List<T> MinByAll<T, C>(this IEnumerable<T> source, Func<T, C> selector) where C : IComparable<C> {
-            var results = new List<T>();
-            using (var enumerator = source.GetEnumerator()) {
-                if (!enumerator.MoveNext()) return results;
-                T minItem = enumerator.Current;
-                C minValue = selector(minItem);
-                results.Add(minItem);
-                while (enumerator.MoveNext()) {
-                    T currentItem = enumerator.Current;
-                    C currentValue = selector(currentItem);
-                    int comparisonResult = currentValue.CompareTo(minValue);
-                    if (comparisonResult < 0) {
-                        minValue = currentValue;
-                        results.Clear();
-                        results.Add(currentItem);
-                    } else if (comparisonResult == 0) {
-                        results.Add(currentItem);
-                    }
-                }
+        return results;
+    }
+    /// <summary>
+    /// 选择所有最小值对应的对象。
+    /// 若没有元素则返回空列表。
+    /// </summary>
+    public static List<T> MinByAll<T, C>(this IEnumerable<T> source, Func<T, C> selector) where C : IComparable<C> {
+        var results = new List<T>();
+        using var enumerator = source.GetEnumerator();
+        if (!enumerator.MoveNext()) return results;
+        T minItem = enumerator.Current;
+        C minValue = selector(minItem);
+        results.Add(minItem);
+        while (enumerator.MoveNext()) {
+            T currentItem = enumerator.Current;
+            C currentValue = selector(currentItem);
+            int comparisonResult = currentValue.CompareTo(minValue);
+            if (comparisonResult < 0) {
+                minValue = currentValue;
+                results.Clear();
+                results.Add(currentItem);
+            } else if (comparisonResult == 0) {
+                results.Add(currentItem);
             }
-            return results;
         }
+        return results;
+    }
 
-        /// <summary>
-        /// 选择最大值对应的对象。
-        /// 若没有元素则返回 Nothing。
-        /// </summary>
-        public static T MaxBy<T, C>(this IEnumerable<T> source, Func<T, C> selector) where C : IComparable<C> {
-            using (var enumerator = source.GetEnumerator()) {
-                if (!enumerator.MoveNext()) return default;
-                T maxItem = enumerator.Current;
-                C maxValue = selector(maxItem);
-                while (enumerator.MoveNext()) {
-                    C value = selector(enumerator.Current);
-                    if (value.CompareTo(maxValue) <= 0) continue;
-                    maxItem = enumerator.Current;
-                    maxValue = value;
-                }
-                return maxItem;
-            }
+    /// <summary>
+    /// 选择最大值对应的对象。
+    /// 若没有元素则返回 Nothing。
+    /// </summary>
+    public static T MaxBy<T, C>(this IEnumerable<T> source, Func<T, C> selector) where C : IComparable<C> {
+        using var enumerator = source.GetEnumerator();
+        if (!enumerator.MoveNext()) return default;
+        T maxItem = enumerator.Current;
+        C maxValue = selector(maxItem);
+        while (enumerator.MoveNext()) {
+            C value = selector(enumerator.Current);
+            if (value.CompareTo(maxValue) <= 0) continue;
+            maxItem = enumerator.Current;
+            maxValue = value;
         }
-        /// <summary>
-        /// 选择最小值对应的对象。
-        /// 若没有元素则返回 Nothing。
-        /// </summary>
-        public static T MinBy<T, C>(this IEnumerable<T> List, Func<T, C> Selector) where C : IComparable<C> {
-            using (var enumerator = List.GetEnumerator()) {
-                if (!enumerator.MoveNext()) { return default; }
-                T minItem = enumerator.Current;
-                C minValue = Selector(minItem);
-                while (enumerator.MoveNext()) {
-                    C value = Selector(enumerator.Current);
-                    if (value.CompareTo(minValue) >= 0) { continue; }
-                    minItem = enumerator.Current;
-                    minValue = value;
-                }
-                return minItem;
-            }
+        return maxItem;
+    }
+    /// <summary>
+    /// 选择最小值对应的对象。
+    /// 若没有元素则返回 Nothing。
+    /// </summary>
+    public static T MinBy<T, C>(this IEnumerable<T> List, Func<T, C> Selector) where C : IComparable<C> {
+        using var enumerator = List.GetEnumerator();
+        if (!enumerator.MoveNext()) { return default; }
+        T minItem = enumerator.Current;
+        C minValue = Selector(minItem);
+        while (enumerator.MoveNext()) {
+            C value = Selector(enumerator.Current);
+            if (value.CompareTo(minValue) >= 0) { continue; }
+            minItem = enumerator.Current;
+            minValue = value;
         }
+        return minItem;
+    }
 
-        #endregion
+    #endregion
 
-        #region Join
+    #region Join
 
-        /// <summary>
-        /// 用指定的分割符将集合连接为字符串。
-        /// </summary>
-        public static string Join(this IEnumerable list, char split) {
-            StringBuilder builder = new StringBuilder();
-            bool isFirst = true;
-            foreach (var element in list) {
-                if (isFirst)
-                    isFirst = false;
-                else
-                    builder.Append(split);
-                if (element != null) builder.Append(element.ToString());
-            }
-            return builder.ToString();
+    /// <summary>
+    /// 用指定的分割符将集合连接为字符串。
+    /// </summary>
+    public static string Join(this IEnumerable list, char split) {
+        var builder = new StringBuilder();
+        bool isFirst = true;
+        foreach (var element in list) {
+            if (isFirst)
+                isFirst = false;
+            else
+                builder.Append(split);
+            if (element != null) builder.Append(element.ToString());
         }
-        /// <summary>
-        /// 用指定的分割符将集合连接为字符串。
-        /// </summary>
-        public static string Join(this IEnumerable list, string split) {
-            if (split.IsSingle()) return list.Join(split[0]);
-            StringBuilder builder = new StringBuilder();
-            bool isFirst = true;
-            foreach (var element in list) {
-                if (isFirst)
-                    isFirst = false;
-                else
-                    builder.Append(split);
-                if (element != null) builder.Append(element.ToString());
-            }
-            return builder.ToString();
+        return builder.ToString();
+    }
+    /// <summary>
+    /// 用指定的分割符将集合连接为字符串。
+    /// </summary>
+    public static string Join(this IEnumerable list, string split) {
+        if (split.IsSingle()) return list.Join(split[0]);
+        var builder = new StringBuilder();
+        bool isFirst = true;
+        foreach (var element in list) {
+            if (isFirst)
+                isFirst = false;
+            else
+                builder.Append(split);
+            if (element != null) builder.Append(element.ToString());
         }
+        return builder.ToString();
+    }
 
-        #endregion
-
-        /// <summary>
-        /// 判断集合是否有且仅有一个元素。
-        /// </summary>
-        public static bool IsSingle<T>(this IEnumerable<T> source) {
-            if (source == null) return false;
-            if (source is IList<T> list) return list.Count == 1;
-            using (var enumerator = source.GetEnumerator()) {
-                if (!enumerator.MoveNext()) return false;
-                if (enumerator.MoveNext()) return false;
-                return true;
-            }
-        }
+    #endregion
 
-        /// <summary>
-        /// 对集合的每个元素执行指定操作。
-        /// </summary>
-        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) {
-            foreach (T item in source) action(item);
-            return source;
-        }
+    /// <summary>
+    /// 判断集合是否有且仅有一个元素。
+    /// </summary>
+    public static bool IsSingle<T>(this IEnumerable<T> source) {
+        if (source == null) return false;
+        if (source is IList<T> list) return list.Count == 1;
+        using var enumerator = source.GetEnumerator();
+        if (!enumerator.MoveNext()) return false;
+        if (enumerator.MoveNext()) return false;
+        return true;
+    }
 
+    /// <summary>
+    /// 对集合的每个元素执行指定操作。
+    /// </summary>
+    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) {
+        foreach (T item in source) action(item);
+        return source;
     }
-}
+
+}

+ 168 - 162
Extensions/StringExtensions.cs

@@ -2,168 +2,174 @@
 using System.Linq;
 using System.Runtime.CompilerServices;
 
-namespace MeloongCore.Extensions {
-    public static class StringExtensions {
-
-        #region 区域性 / 大小写 简化
-
-        /// <summary>
-        /// 高速的 StartsWith。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static bool StartsWithF(this string value, string prefix, bool ignoreCase = false) {
-            if (value == null) return false;
-            return value.StartsWith(prefix, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
-        }
-        /// <summary>
-        /// 高速的 EndsWith。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static bool EndsWithF(this string value, string suffix, bool ignoreCase = false) {
-            if (value == null) return false;
-            return value.EndsWith(suffix, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
-        }
-
-        /// <summary>
-        /// 忽略大小写的 Contains。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static bool ContainsIgnoreCase(this string value, string subString) {
-            return value.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0;
-        }
-
-        /// <summary>
-        /// 高速的 IndexOf。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static int IndexOfF(this string value, string subString, bool ignoreCase = false) {
-            return value.IndexOf(subString, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
-        }
-        /// <summary>
-        /// 高速的 IndexOf。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static int IndexOfF(this string value, string subString, int startIndex, bool ignoreCase = false) {
-            return value.IndexOf(subString, startIndex, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
-        }
-
-        /// <summary>
-        /// 高速的 LastIndexOf。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static int LastIndexOfF(this string value, string subString, bool ignoreCase = false) {
-            return value.LastIndexOf(subString, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
-        }
-        /// <summary>
-        /// 高速的 LastIndexOf。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static int LastIndexOfF(this string value, string subString, int startIndex, bool ignoreCase = false) {
-            return value.LastIndexOf(subString, startIndex, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
-        }
-
-        /// <summary>
-        /// ToLowerInvariant 的简略写法。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static string Lower(this string str) => str.ToLowerInvariant();
-        /// <summary>
-        /// ToUpperInvariant 的简略写法。
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static string Upper(this string str) => str.ToUpperInvariant();
-
-        #endregion
-
-        #region 分割与裁切
-
-        /// <summary>
-        /// 分割字符串。
-        /// 若原始字符串为空,则返回 {""}。
-        /// </summary>
-        public static string[] Split(this string fullStr, string splitStr) {
-            if (splitStr.IsSingle())
-                return fullStr.Split(splitStr[0]);
-            else
-                return fullStr.Split(new[] {splitStr}, StringSplitOptions.None);
-        }
-
-        /// <summary>
-        /// 获取在子字符串第一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024。
-        /// 如果未找到子字符串则不裁切。
-        /// </summary>
-        public static string BeforeFirst(this string str, string text, bool ignoreCase = false) {
-            int pos = string.IsNullOrEmpty(text) ? -1 : str.IndexOfF(text, ignoreCase);
-            if (pos >= 0)
-                return str.Substring(0, pos);
-            else
-                return str;
-        }
-        /// <summary>
-        /// 获取在子字符串最后一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024/11。
-        /// 如果未找到子字符串则不裁切。
-        /// </summary>
-        public static string BeforeLast(this string str, string text, bool ignoreCase = false) {
-            int pos = string.IsNullOrEmpty(text) ? -1 : str.LastIndexOfF(text, ignoreCase);
-            if (pos >= 0)
-                return str.Substring(0, pos);
-            else
-                return str;
-        }
-
-        /// <summary>
-        /// 获取在子字符串第一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 11/08。
-        /// 如果未找到子字符串则不裁切。
-        /// </summary>
-        public static string AfterFirst(this string str, string text, bool ignoreCase = false) {
-            int pos = string.IsNullOrEmpty(text) ? -1 : str.IndexOfF(text, ignoreCase);
-            if (pos >= 0)
-                return str.Substring(pos + text.Length);
-            else
-                return str;
-        }
-        /// <summary>
-        /// 获取在子字符串最后一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 08。
-        /// 如果未找到子字符串则不裁切。
-        /// </summary>
-        public static string AfterLast(this string str, string text, bool ignoreCase = false) {
-            int pos = string.IsNullOrEmpty(text) ? -1 : str.LastIndexOfF(text, ignoreCase);
-            if (pos >= 0)
-                return str.Substring(pos + text.Length);
-            else
-                return str;
-        }
-
-        /// <summary>
-        /// 获取处于两个子字符串之间的部分,裁切尽可能多的内容。
-        /// 等效于 AfterLast 后接 BeforeFirst。
-        /// 如果未找到子字符串则不裁切。
-        /// </summary>
-        public static string Between(this string str, string after, string before, bool ignoreCase = false) {
-            int startPos = string.IsNullOrEmpty(after) ? -1 : str.LastIndexOfF(after, ignoreCase);
-            if (startPos >= 0)
-                startPos += after.Length;
-            else
-                startPos = 0;
-
-            int endPos = string.IsNullOrEmpty(before) ? -1 : str.IndexOfF(before, startPos, ignoreCase);
-            if (endPos >= 0)
-                return str.Substring(startPos, endPos - startPos);
-            else if (startPos > 0)
-                return str.Substring(startPos);
-            else
-                return str;
-        }
-
-        #endregion
-
-        /// <summary>
-        /// 将第一个字符转换为大写,其余字符转换为小写。
-        /// </summary>
-        public static string Capitalize(this string word) {
-            if (string.IsNullOrEmpty(word)) return word;
-            return word.Substring(0, 1).Upper() + word.Substring(1).Lower();
-        }
+namespace MeloongCore.Extensions;
+public static class StringExtensions {
+
+    #region 区域性 / 大小写 简化
+
+    /// <summary>
+    /// 高速的 StartsWith。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool StartsWithF(this string value, string prefix, bool ignoreCase = false) {
+        if (value == null) return false;
+        return value.StartsWith(prefix, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
+    /// <summary>
+    /// 高速的 EndsWith。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool EndsWithF(this string value, string suffix, bool ignoreCase = false) {
+        if (value == null) return false;
+        return value.EndsWith(suffix, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
+
+    /// <summary>
+    /// 忽略大小写的 Contains。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool ContainsIgnoreCase(this string value, string subString) {
+        return value.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0;
+    }
+
+    /// <summary>
+    /// 高速的 IndexOf。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static int IndexOfF(this string value, string subString, bool ignoreCase = false) {
+        return value.IndexOf(subString, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
+    /// <summary>
+    /// 高速的 IndexOf。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static int IndexOfF(this string value, string subString, int startIndex, bool ignoreCase = false) {
+        return value.IndexOf(subString, startIndex, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
+
+    /// <summary>
+    /// 高速的 LastIndexOf。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static int LastIndexOfF(this string value, string subString, bool ignoreCase = false) {
+        return value.LastIndexOf(subString, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
+    /// <summary>
+    /// 高速的 LastIndexOf。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static int LastIndexOfF(this string value, string subString, int startIndex, bool ignoreCase = false) {
+        return value.LastIndexOf(subString, startIndex, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
+
+    /// <summary>
+    /// ToLowerInvariant 的简略写法。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static string Lower(this string str) => str.ToLowerInvariant();
+    /// <summary>
+    /// ToUpperInvariant 的简略写法。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static string Upper(this string str) => str.ToUpperInvariant();
+
+    #endregion
+
+    #region 分割与裁切
+
+    /// <summary>
+    /// 分割字符串。
+    /// 若原始字符串为空,则返回 {""}。
+    /// </summary>
+    public static string[] Split(this string fullStr, string splitStr) {
+        if (splitStr.IsSingle())
+            return fullStr.Split(splitStr[0]);
+        else
+            return fullStr.Split(new[] {splitStr}, StringSplitOptions.None);
+    }
 
+    /// <summary>
+    /// 获取在子字符串第一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024。
+    /// 如果未找到子字符串则不裁切。
+    /// </summary>
+    public static string BeforeFirst(this string str, string text, bool ignoreCase = false) {
+        int pos = string.IsNullOrEmpty(text) ? -1 : str.IndexOfF(text, ignoreCase);
+        if (pos >= 0)
+            return str.Substring(0, pos);
+        else
+            return str;
     }
+    /// <summary>
+    /// 获取在子字符串最后一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024/11。
+    /// 如果未找到子字符串则不裁切。
+    /// </summary>
+    public static string BeforeLast(this string str, string text, bool ignoreCase = false) {
+        int pos = string.IsNullOrEmpty(text) ? -1 : str.LastIndexOfF(text, ignoreCase);
+        if (pos >= 0)
+            return str.Substring(0, pos);
+        else
+            return str;
+    }
+
+    /// <summary>
+    /// 获取在子字符串第一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 11/08。
+    /// 如果未找到子字符串则不裁切。
+    /// </summary>
+    public static string AfterFirst(this string str, string text, bool ignoreCase = false) {
+        int pos = string.IsNullOrEmpty(text) ? -1 : str.IndexOfF(text, ignoreCase);
+        if (pos >= 0)
+            return str.Substring(pos + text.Length);
+        else
+            return str;
+    }
+    /// <summary>
+    /// 获取在子字符串最后一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 08。
+    /// 如果未找到子字符串则不裁切。
+    /// </summary>
+    public static string AfterLast(this string str, string text, bool ignoreCase = false) {
+        int pos = string.IsNullOrEmpty(text) ? -1 : str.LastIndexOfF(text, ignoreCase);
+        if (pos >= 0)
+            return str.Substring(pos + text.Length);
+        else
+            return str;
+    }
+
+    /// <summary>
+    /// 获取处于两个子字符串之间的部分,裁切尽可能多的内容。
+    /// 等效于 AfterLast 后接 BeforeFirst。
+    /// 如果未找到子字符串则不裁切。
+    /// </summary>
+    public static string Between(this string str, string after, string before, bool ignoreCase = false) {
+        int startPos = string.IsNullOrEmpty(after) ? -1 : str.LastIndexOfF(after, ignoreCase);
+        if (startPos >= 0)
+            startPos += after.Length;
+        else
+            startPos = 0;
+
+        int endPos = string.IsNullOrEmpty(before) ? -1 : str.IndexOfF(before, startPos, ignoreCase);
+        if (endPos >= 0)
+            return str.Substring(startPos, endPos - startPos);
+        else if (startPos > 0)
+            return str.Substring(startPos);
+        else
+            return str;
+    }
+
+    #endregion
+
+    /// <summary>
+    /// 将第一个字符转换为大写,其余字符转换为小写。
+    /// </summary>
+    public static string Capitalize(this string word) {
+        if (string.IsNullOrEmpty(word)) return word;
+        return $"{word.Substring(0, 1).Upper()}{word.Substring(1).Lower()}";
+    }
+
+    /// <summary>
+    /// 替换字符串中的内容。
+    /// 仅当需要替换时,才调用 <paramref name="newValue"/> 获取结果字符串。
+    /// </summary>
+    public static string ReplaceOnDemand(this string str, string oldValue, Func<string> newValue) =>
+        str.Contains(oldValue) ? str.Replace(oldValue, newValue()) : str;
+
 }

+ 0 - 32
General/DirectoryUtils.cs

@@ -1,32 +0,0 @@
-using MeloongCore.Extensions;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace MeloongCore {
-    public static class DirectoryUtils {
-
-        /// <summary>
-        /// 确保路径以 / 或 \ 结尾。
-        /// </summary>
-        public static string ToDirectoryFormat(string folder) => IsDirectoryFormat(folder) ? folder : folder + Path.DirectorySeparatorChar;
-
-        /// <summary>
-        /// 检查路径是否以 / 或 \ 结尾。
-        /// </summary>
-        public static bool IsDirectoryFormat(string path) => path.EndsWithF("/") || path.EndsWithF("\\");
-
-        /// <summary>
-        /// 创建文件夹,或文件所在的文件夹。
-        /// 支持长路径,文件夹已存在时不会抛出异常。
-        /// </summary>
-        public static void Create(string path, bool isFilePath = false) {
-            if (isFilePath) path = Path.GetDirectoryName(path);
-            Directory.CreateDirectory(PathUtils.Shorten(path));
-        }
-
-    }
-}

+ 0 - 109
General/MathUtils.cs

@@ -1,109 +0,0 @@
-using MeloongCore.Extensions;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-
-namespace MeloongCore {
-    public static class MathUtils {
-
-        #region 进制转换
-
-        /// <summary>
-        /// 将字符串作为数字,转换为指定进制的形式。
-        /// 进制数需在 2 到 86 之间,且输入字符串中的每个字符都必须在该进制的字符集中。
-        /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
-        /// </summary>
-        public static string RadixConvert(this string input, int fromRadix, int toRadix) {
-            const string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+=!?@#$%^&*()[]{}<>;:',";
-            if (fromRadix < 2 || fromRadix > digits.Length) throw new ArgumentOutOfRangeException(nameof(fromRadix), $"{nameof(fromRadix)} must be between 2 and 86.");
-            // 零与负数的预处理
-            if (string.IsNullOrEmpty(input)) return "0";
-            bool isNegative = input.StartsWithF("-");
-            if (isNegative) input = input.TrimStart('-');
-            // 转换为十进制
-            BigInteger realNum = 0;
-            foreach (char c in input) {
-                int digit = digits.IndexOf(c);
-                if (digit == -1 || digit >= fromRadix) throw new ArgumentException($"Character '{c}' in input '{input}' is not a valid digit for radix {fromRadix}.");
-                realNum = realNum * fromRadix + digit;
-            }
-            return RadixConvert(realNum * (isNegative ? -1 : 1), toRadix);
-        }
-        /// <summary>
-        /// 转换为 2 到 86 进制的字符串。
-        /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
-        /// </summary>
-        public static string RadixConvert(this long input, int toRadix) {
-            return RadixConvert((BigInteger) input, toRadix);
-        }
-        /// <summary>
-        /// 转换为 2 到 86 进制的字符串。
-        /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
-        /// </summary>
-        public static string RadixConvert(this int input, int toRadix) {
-            return RadixConvert((BigInteger) input, toRadix);
-        }
-        /// <summary>
-        /// 转换为 2 到 86 进制的字符串。
-        /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
-        /// </summary>
-        public static string RadixConvert(this byte input, int toRadix) {
-            return RadixConvert((BigInteger) input, toRadix);
-        }
-        private static string RadixConvert(this BigInteger input, int toRadix) {
-            const string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+=!?@#$%^&*()[]{}<>;:',";
-            if (toRadix < 2 || toRadix > digits.Length) throw new ArgumentOutOfRangeException(nameof(toRadix), $"{nameof(toRadix)} must be between 2 and 86.");
-            // 零与负数的预处理
-            if (input == 0) return "0";
-            bool isNegative = input < 0;
-            // 转换为指定进制
-            var results = new List<char>();
-            BigInteger remaining = BigInteger.Abs(input);
-            BigInteger bigRadix = toRadix;
-            while (remaining > 0) {
-                remaining = BigInteger.DivRem(remaining, bigRadix, out BigInteger remainder);
-                results.Add(digits[(int) remainder]);
-            }
-            // 负数的结束处理与返回
-            if (isNegative) results.Add('-');
-            results.Reverse();
-            return new string(results.ToArray());
-        }
-
-        #endregion
-
-        /// <summary>
-        /// 将值限制在指定范围内。
-        /// </summary>
-        public static T Clamp<T>(this T value, T min, T max) where T : IComparable<T> {
-            if (value.CompareTo(min) < 0) return min;
-            if (value.CompareTo(max) > 0) return max;
-            return value;
-        }
-
-        /// <summary>
-        /// 在一个范围中进行线性插值。
-        /// 返回值将被舍入到小数点后 7 位,以解决浮点数误差问题。
-        /// </summary>
-        public static double Lerp(double start, double end, double percentage) {
-            return Math.Round(start + (end - start) * percentage, 7);
-        }
-
-        /// <summary>
-        /// 计算二阶贝塞尔曲线。
-        /// </summary>
-        public static double Bezier(double t, double x1, double y1, double x2, double y2, double accuracy = 0.01) {
-            if (t < 0.0000001 || double.IsNaN(t)) return 0;
-            if (t > 0.9999999) return 1;
-            double a = t;
-            double b;
-            do {
-                b = 3 * a * ((0.33333333 + x1 - x2) * a * a + (x2 - 2 * x1) * a + x1);
-                a += (t - b) * 0.5;
-            } while (!(Math.Abs(b - t) < accuracy)); // 精度
-            return 3 * a * ((0.33333333 + y1 - y2) * a * a + (y2 - 2 * y1) * a + y1);
-        }
-
-    }
-}

+ 49 - 51
General/MsDelta.cs

@@ -3,61 +3,59 @@ using System.ComponentModel;
 using System.IO;
 using System.Runtime.InteropServices;
 
-namespace MeloongCore {
+namespace MeloongCore;
+
+/// <summary>
+/// 使用 Windows MsDelta API 生成、应用增量补丁文件。
+/// </summary>
+public static class MsDelta {
 
     /// <summary>
-    /// 使用 Windows MsDelta API 生成、应用增量补丁文件。
+    /// 生成增量补丁文件:比较 <paramref name="oldFilePath"/> 和 <paramref name="newFilePath"/>,将补丁写入 <paramref name="deltaFilePath"/>。
     /// </summary>
-    public static class MsDelta {
-
-        /// <summary>
-        /// 生成增量补丁文件:比较 <paramref name="oldFilePath"/> 和 <paramref name="newFilePath"/>,将补丁写入 <paramref name="deltaFilePath"/>。
-        /// </summary>
-        /// <exception cref="FileNotFoundException" />
-        /// <exception cref="Win32Exception" />
-        public static void Create(string oldFilePath, string newFilePath, string deltaFilePath) {
-            if (!File.Exists(oldFilePath)) throw new FileNotFoundException($"旧文件不存在:{oldFilePath}", oldFilePath);
-            if (!File.Exists(newFilePath)) throw new FileNotFoundException($"新文件不存在:{newFilePath}", newFilePath);
-            DirectoryUtils.Create(deltaFilePath, isFilePath: true);
-            // fileTypeSet:https://learn.microsoft.com/en-us/previous-versions/bb417345(v=msdn.10)?redirectedfrom=MSDN#file-type-sets (15L: DELTA_FILE_TYPE_SET_EXECUTABLES)
-            // 131072L: IgnoreFileSizeLimit, 32u: Crc32
-            if (!CreateDelta(15L, 131072L, 0L, 
-                PathUtils.Shorten(oldFilePath), PathUtils.Shorten(newFilePath), null, null, new DeltaInput(), IntPtr.Zero, 32u, PathUtils.Shorten(deltaFilePath)))
-                throw new Win32Exception();
-        }
-
-        /// <summary>
-        /// 应用增量补丁文件:将 <paramref name="deltaFilePath"/> 补丁应用到 <paramref name="oldFilePath"/>,将结果写入 <paramref name="newFilePath"/>。
-        /// </summary>
-        /// <exception cref="FileNotFoundException" />
-        /// <exception cref="Win32Exception" />
-        public static void Apply(string oldFilePath, string deltaFilePath, string newFilePath) {
-            if (!File.Exists(oldFilePath)) throw new FileNotFoundException($"旧文件不存在:{oldFilePath}", oldFilePath);
-            if (!File.Exists(deltaFilePath)) throw new FileNotFoundException($"补丁文件不存在:{deltaFilePath}", deltaFilePath);
-            DirectoryUtils.Create(newFilePath, isFilePath: true);
-            // 1L: AllowLegacy
-            if (!ApplyDelta(1L, PathUtils.Shorten(oldFilePath), PathUtils.Shorten(deltaFilePath), PathUtils.Shorten(newFilePath)))
-                throw new Win32Exception();
-        }
+    /// <exception cref="FileNotFoundException" />
+    /// <exception cref="Win32Exception" />
+    public static void Create(string oldFilePath, string newFilePath, string deltaFilePath) {
+        if (!File.Exists(oldFilePath)) throw new FileNotFoundException($"旧文件不存在:{oldFilePath}", oldFilePath);
+        if (!File.Exists(newFilePath)) throw new FileNotFoundException($"新文件不存在:{newFilePath}", newFilePath);
+        DirectoryUtils.Create(deltaFilePath, isFilePath: true);
+        // fileTypeSet:https://learn.microsoft.com/en-us/previous-versions/bb417345(v=msdn.10)?redirectedfrom=MSDN#file-type-sets (15L: DELTA_FILE_TYPE_SET_EXECUTABLES)
+        // 131072L: IgnoreFileSizeLimit, 32u: Crc32
+        if (!CreateDelta(15L, 131072L, 0L, 
+            PathUtils.Shorten(oldFilePath), PathUtils.Shorten(newFilePath), null, null, new DeltaInput(), IntPtr.Zero, 32u, PathUtils.Shorten(deltaFilePath)))
+            throw new Win32Exception();
+    }
 
+    /// <summary>
+    /// 应用增量补丁文件:将 <paramref name="deltaFilePath"/> 补丁应用到 <paramref name="oldFilePath"/>,将结果写入 <paramref name="newFilePath"/>。
+    /// </summary>
+    /// <exception cref="FileNotFoundException" />
+    /// <exception cref="Win32Exception" />
+    public static void Apply(string oldFilePath, string deltaFilePath, string newFilePath) {
+        if (!File.Exists(oldFilePath)) throw new FileNotFoundException($"旧文件不存在:{oldFilePath}", oldFilePath);
+        if (!File.Exists(deltaFilePath)) throw new FileNotFoundException($"补丁文件不存在:{deltaFilePath}", deltaFilePath);
+        DirectoryUtils.Create(newFilePath, isFilePath: true);
+        // 1L: AllowLegacy
+        if (!ApplyDelta(1L, PathUtils.Shorten(oldFilePath), PathUtils.Shorten(deltaFilePath), PathUtils.Shorten(newFilePath)))
+            throw new Win32Exception();
+    }
 
-        [StructLayout(LayoutKind.Sequential)]
-        private struct DeltaInput {
-            public IntPtr Start;
-            public IntPtr Size;
-            [MarshalAs(UnmanagedType.Bool)] public bool Editable;
-        }
-        [DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
-        [return: MarshalAs(UnmanagedType.Bool)]
-        private static extern bool CreateDelta(
-            long fileTypeSet, long setFlags, long resetFlags,
-            string sourceName, string targetName, string sourceOptionsName,
-            string targetOptionsName, DeltaInput globalOptions,
-            IntPtr targetFileTime, uint hashAlgId, string deltaName);
-        [DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
-        [return: MarshalAs(UnmanagedType.Bool)]
-        private static extern bool ApplyDelta(long applyFlags, string sourceName, string deltaName, string targetName);
 
+    [StructLayout(LayoutKind.Sequential)]
+    private struct DeltaInput {
+        public IntPtr Start;
+        public IntPtr Size;
+        [MarshalAs(UnmanagedType.Bool)] public bool Editable;
     }
-
-}
+    [DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+    [return: MarshalAs(UnmanagedType.Bool)]
+    private static extern bool CreateDelta(
+        long fileTypeSet, long setFlags, long resetFlags,
+        string sourceName, string targetName, string sourceOptionsName,
+        string targetOptionsName, DeltaInput globalOptions,
+        IntPtr targetFileTime, uint hashAlgId, string deltaName);
+    [DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+    [return: MarshalAs(UnmanagedType.Bool)]
+    private static extern bool ApplyDelta(long applyFlags, string sourceName, string deltaName, string targetName);
+
+}

+ 0 - 44
General/PathUtils.cs

@@ -1,44 +0,0 @@
-using MeloongCore.Extensions;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace MeloongCore {
-    public static class PathUtils {
-
-        /// <summary>
-        /// 若路径较长,则尽量将其转换为短路径。
-        /// 若输入的是文件夹路径,不保证其结尾是否有 \ 。
-        /// </summary>
-        public static string Shorten(string fullName, bool keepFileName = false) {
-            if (string.IsNullOrEmpty(fullName) || fullName.Length <= 200) return fullName;
-            fullName = fullName.Replace('/', '\\');
-
-            // 保留文件名
-            string pathToKeep = "";
-            string pathToShorten = fullName;
-            if (fullName.EndsWithF(".jar", true)) keepFileName = true; // jar 文件的文件名需要保留原样,否则会导致 Forge 1.20.1 无法通过文件名识别模块名
-            if (keepFileName && File.Exists(fullName)) {
-                pathToKeep = Path.GetFileName(fullName);
-                pathToShorten = Path.GetDirectoryName(fullName);
-            }
-
-            // 逐级向上寻找已存在的文件夹,将不存在的部分挪到 suffix,不再缩短
-            while (!Directory.Exists(pathToShorten) && !File.Exists(pathToShorten)) { // 如果路径不存在
-                string parentPath = Path.GetDirectoryName(pathToShorten);
-                if (string.IsNullOrEmpty(parentPath) || parentPath == pathToShorten) return fullName; // 已经到达根目录,全都不存在,直接返回
-                pathToKeep = Path.Combine(Path.GetFileName(pathToShorten), pathToKeep);
-                pathToShorten = parentPath;
-            }
-            if (pathToShorten.Length <= 10) return fullName;
-
-            // 缩短路径
-            StringBuilder buffer = new StringBuilder(260);
-            if (GetShortPathNameW(pathToShorten, buffer, buffer.Capacity) == 0) return fullName;
-            return Path.Combine(buffer.ToString(), pathToKeep);
-        }
-        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
-        private static extern int GetShortPathNameW(string lpszLongPath, StringBuilder lpszShortPath, int cchBuffer);
-
-    }
-}

+ 1 - 0
MeloongCore.csproj

@@ -9,6 +9,7 @@
 	<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
 	<Configurations>Debug;Release;Snapshot</Configurations>
 	<Deterministic>true</Deterministic>
+	<LangVersion>12.0</LangVersion>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

+ 31 - 0
Utils/DirectoryUtils.cs

@@ -0,0 +1,31 @@
+using MeloongCore.Extensions;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MeloongCore;
+public static class DirectoryUtils {
+
+    /// <summary>
+    /// 确保路径以 / 或 \ 结尾。
+    /// </summary>
+    public static string ToDirectoryFormat(string folder) => IsDirectoryFormat(folder) ? folder : folder + Path.DirectorySeparatorChar;
+
+    /// <summary>
+    /// 检查路径是否以 / 或 \ 结尾。
+    /// </summary>
+    public static bool IsDirectoryFormat(string path) => path.EndsWithF("/") || path.EndsWithF("\\");
+
+    /// <summary>
+    /// 创建文件夹,或文件所在的文件夹。
+    /// 支持长路径,文件夹已存在时不会抛出异常。
+    /// </summary>
+    public static void Create(string path, bool isFilePath = false) {
+        if (isFilePath) path = Path.GetDirectoryName(path);
+        Directory.CreateDirectory(PathUtils.Shorten(path));
+    }
+
+}

+ 101 - 0
Utils/MathUtils.cs

@@ -0,0 +1,101 @@
+using MeloongCore.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+namespace MeloongCore;
+
+public static class MathUtils {
+
+    #region 进制转换
+
+    /// <summary>
+    /// 将字符串作为数字,转换为指定进制的形式。
+    /// 进制数需在 2 到 86 之间,且输入字符串中的每个字符都必须在该进制的字符集中。
+    /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
+    /// </summary>
+    public static string RadixConvert(this string input, int fromRadix, int toRadix) {
+        const string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+=!?@#$%^&*()[]{}<>;:',";
+        if (fromRadix < 2 || fromRadix > digits.Length) throw new ArgumentOutOfRangeException(nameof(fromRadix), $"{nameof(fromRadix)} must be between 2 and 86.");
+        // 零与负数的预处理
+        if (string.IsNullOrEmpty(input)) return "0";
+        bool isNegative = input.StartsWithF("-");
+        if (isNegative) input = input.TrimStart('-');
+        // 转换为十进制
+        BigInteger realNum = 0;
+        foreach (char c in input) {
+            int digit = digits.IndexOf(c);
+            if (digit == -1 || digit >= fromRadix) throw new ArgumentException($"Character '{c}' in input '{input}' is not a valid digit for radix {fromRadix}.");
+            realNum = realNum * fromRadix + digit;
+        }
+        return RadixConvert(realNum * (isNegative ? -1 : 1), toRadix);
+    }
+    /// <summary>
+    /// 转换为 2 到 86 进制的字符串。
+    /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
+    /// </summary>
+    public static string RadixConvert(this long input, int toRadix) => RadixConvert((BigInteger) input, toRadix);
+    /// <summary>
+    /// 转换为 2 到 86 进制的字符串。
+    /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
+    /// </summary>
+    public static string RadixConvert(this int input, int toRadix) => RadixConvert((BigInteger) input, toRadix);
+    /// <summary>
+    /// 转换为 2 到 86 进制的字符串。
+    /// 进制参考:含大写字母 36,含大小写字母 62,含大小写字母和特殊符号 86
+    /// </summary>
+    public static string RadixConvert(this byte input, int toRadix) => RadixConvert((BigInteger) input, toRadix);
+    private static string RadixConvert(this BigInteger input, int toRadix) {
+        const string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+=!?@#$%^&*()[]{}<>;:',";
+        if (toRadix < 2 || toRadix > digits.Length) throw new ArgumentOutOfRangeException(nameof(toRadix), $"{nameof(toRadix)} must be between 2 and 86.");
+        // 零与负数的预处理
+        if (input == 0) return "0";
+        bool isNegative = input < 0;
+        // 转换为指定进制
+        var results = new List<char>();
+        BigInteger remaining = BigInteger.Abs(input);
+        BigInteger bigRadix = toRadix;
+        while (remaining > 0) {
+            remaining = BigInteger.DivRem(remaining, bigRadix, out BigInteger remainder);
+            results.Add(digits[(int) remainder]);
+        }
+        // 负数的结束处理与返回
+        if (isNegative) results.Add('-');
+        results.Reverse();
+        return new string(results.ToArray());
+    }
+
+    #endregion
+
+    /// <summary>
+    /// 将值限制在指定范围内。
+    /// </summary>
+    public static T Clamp<T>(this T value, T min, T max) where T : IComparable<T> {
+        if (value.CompareTo(min) < 0) return min;
+        if (value.CompareTo(max) > 0) return max;
+        return value;
+    }
+
+    /// <summary>
+    /// 在一个范围中进行线性插值。
+    /// 返回值将被舍入到小数点后 7 位,以解决浮点数误差问题。
+    /// </summary>
+    public static double Lerp(double start, double end, double percentage) => 
+        Math.Round(start + (end - start) * percentage, 7);
+
+    /// <summary>
+    /// 计算二阶贝塞尔曲线。
+    /// </summary>
+    public static double Bezier(double t, double x1, double y1, double x2, double y2, double accuracy = 0.01) {
+        if (t < 0.0000001 || double.IsNaN(t)) return 0;
+        if (t > 0.9999999) return 1;
+        double a = t;
+        double b;
+        do {
+            b = 3 * a * ((0.33333333 + x1 - x2) * a * a + (x2 - 2 * x1) * a + x1);
+            a += (t - b) * 0.5;
+        } while (!(Math.Abs(b - t) < accuracy)); // 精度
+        return 3 * a * ((0.33333333 + y1 - y2) * a * a + (y2 - 2 * y1) * a + y1);
+    }
+
+}

+ 43 - 0
Utils/PathUtils.cs

@@ -0,0 +1,43 @@
+using MeloongCore.Extensions;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+namespace MeloongCore;
+
+public static class PathUtils {
+
+    /// <summary>
+    /// 若路径较长,则尽量将其转换为短路径。
+    /// 若输入的是文件夹路径,不保证其结尾是否有 \ 。
+    /// </summary>
+    public static string Shorten(string fullName, bool keepFileName = false) {
+        if (string.IsNullOrEmpty(fullName) || fullName.Length <= 200) return fullName;
+        fullName = fullName.Replace('/', '\\');
+
+        // 保留文件名
+        string pathToKeep = "";
+        string pathToShorten = fullName;
+        if (fullName.EndsWithF(".jar", true)) keepFileName = true; // jar 文件的文件名需要保留原样,否则会导致 Forge 1.20.1 无法通过文件名识别模块名
+        if (keepFileName && File.Exists(fullName)) {
+            pathToKeep = Path.GetFileName(fullName);
+            pathToShorten = Path.GetDirectoryName(fullName);
+        }
+
+        // 逐级向上寻找已存在的文件夹,将不存在的部分挪到 suffix,不再缩短
+        while (!Directory.Exists(pathToShorten) && !File.Exists(pathToShorten)) { // 如果路径不存在
+            string parentPath = Path.GetDirectoryName(pathToShorten);
+            if (string.IsNullOrEmpty(parentPath) || parentPath == pathToShorten) return fullName; // 已经到达根目录,全都不存在,直接返回
+            pathToKeep = Path.Combine(Path.GetFileName(pathToShorten), pathToKeep);
+            pathToShorten = parentPath;
+        }
+        if (pathToShorten.Length <= 10) return fullName;
+
+        // 缩短路径
+        var buffer = new StringBuilder(260);
+        if (GetShortPathNameW(pathToShorten, buffer, buffer.Capacity) == 0) return fullName;
+        return Path.Combine(buffer.ToString(), pathToKeep);
+    }
+    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+    private static extern int GetShortPathNameW(string lpszLongPath, StringBuilder lpszShortPath, int cchBuffer);
+
+}