Explorar el Código

Code Review 2

龙腾猫跃 hace 5 meses
padre
commit
368280fcb5

+ 29 - 25
Shared/Extensions/EnumerableExtensions.cs

@@ -11,14 +11,18 @@ public static class EnumerableExtensions {
     /// 该方法的效率较低,建议仅在小型列表上使用,或换用 DistinctBy。
     /// </summary>
     public static IEnumerable<T> Distinct<T>(this IList<T> list, Func<T, T, bool> comparer) {
-        for (int i = list.Count - 1; i >= 0; i--) { // 反向遍历以保留最早出现的重复项
-            if (!list.Skip(i + 1).Any(item => comparer(list[i], item))) yield return list[i];
+        var seen = new List<T>();
+        foreach (var item in list) {
+            if (!seen.Any(s => comparer(s, item))) {
+                seen.Add(item);
+                yield return item;
+            }
         }
     }
     /// <summary>
     /// 按对象的指定值去重。
     /// </summary>
-    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector) {
+    public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) {
         var seen = new HashSet<TKey>();
         foreach (var element in source) {
             if (seen.Add(selector(element))) yield return element;
@@ -27,7 +31,7 @@ public static class EnumerableExtensions {
     /// <summary>
     /// 按对象的指定值去重。
     /// </summary>
-    public static ParallelQuery<T> DistinctBy<T, TKey>(this ParallelQuery<T> source, Func<T, TKey> selector) {
+    public static ParallelQuery<TSource> DistinctBy<TSource, TKey>(this ParallelQuery<TSource> source, Func<TSource, TKey> selector) {
         var seen = new ConcurrentDictionary<TKey, bool>();
         return source.Where(element => seen.TryAdd(selector(element), true));
     }
@@ -40,16 +44,16 @@ public static class EnumerableExtensions {
     /// 选择所有最大值对应的对象。
     /// 若没有元素则返回空列表。
     /// </summary>
-    public static List<TKey> MaxByAll<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
-        var results = new List<TKey>();
+    public static List<TSource> MaxByAll<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
+        var results = new List<TSource>();
         using var enumerator = source.GetEnumerator();
         if (!enumerator.MoveNext()) return results;
-        TKey maxItem = enumerator.Current;
-        TValue maxValue = selector(maxItem);
+        TSource maxItem = enumerator.Current;
+        TKey maxValue = selector(maxItem);
         results.Add(maxItem);
         while (enumerator.MoveNext()) {
-            TKey currentItem = enumerator.Current;
-            TValue currentValue = selector(currentItem);
+            TSource currentItem = enumerator.Current;
+            TKey currentValue = selector(currentItem);
             int comparisonResult = currentValue.CompareTo(maxValue);
             if (comparisonResult > 0) {
                 maxValue = currentValue;
@@ -65,16 +69,16 @@ public static class EnumerableExtensions {
     /// 选择所有最小值对应的对象。
     /// 若没有元素则返回空列表。
     /// </summary>
-    public static List<TKey> MinByAll<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
-        var results = new List<TKey>();
+    public static List<TSource> MinByAll<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
+        var results = new List<TSource>();
         using var enumerator = source.GetEnumerator();
         if (!enumerator.MoveNext()) return results;
-        TKey minItem = enumerator.Current;
-        TValue minValue = selector(minItem);
+        TSource minItem = enumerator.Current;
+        TKey minValue = selector(minItem);
         results.Add(minItem);
         while (enumerator.MoveNext()) {
-            TKey currentItem = enumerator.Current;
-            TValue currentValue = selector(currentItem);
+            TSource currentItem = enumerator.Current;
+            TKey currentValue = selector(currentItem);
             int comparisonResult = currentValue.CompareTo(minValue);
             if (comparisonResult < 0) {
                 minValue = currentValue;
@@ -91,13 +95,13 @@ public static class EnumerableExtensions {
     /// 选择最大值对应的对象。
     /// 若没有元素则返回 null。
     /// </summary>
-    public static TKey? MaxBy<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
+    public static TSource? MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
         using var enumerator = source.GetEnumerator();
         if (!enumerator.MoveNext()) return default;
-        TKey maxItem = enumerator.Current;
-        TValue maxValue = selector(maxItem);
+        TSource maxItem = enumerator.Current;
+        TKey maxValue = selector(maxItem);
         while (enumerator.MoveNext()) {
-            TValue value = selector(enumerator.Current);
+            TKey value = selector(enumerator.Current);
             if (value.CompareTo(maxValue) <= 0) continue;
             maxItem = enumerator.Current;
             maxValue = value;
@@ -108,13 +112,13 @@ public static class EnumerableExtensions {
     /// 选择最小值对应的对象。
     /// 若没有元素则返回 null。
     /// </summary>
-    public static TKey? MinBy<TKey, TValue>(this IEnumerable<TKey> source, Func<TKey, TValue> selector) where TValue : IComparable<TValue> {
+    public static TSource? MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) where TKey : IComparable<TKey> {
         using var enumerator = source.GetEnumerator();
         if (!enumerator.MoveNext()) { return default; }
-        TKey minItem = enumerator.Current;
-        TValue minValue = selector(minItem);
+        TSource minItem = enumerator.Current;
+        TKey minValue = selector(minItem);
         while (enumerator.MoveNext()) {
-            TValue value = selector(enumerator.Current);
+            TKey value = selector(enumerator.Current);
             if (value.CompareTo(minValue) >= 0) { continue; }
             minItem = enumerator.Current;
             minValue = value;
@@ -183,7 +187,7 @@ public static class EnumerableExtensions {
     /// <summary>
     /// 尝试从字典中获取某项,如果该项不存在,则返回默认值。
     /// </summary>
-    public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default!) 
+    public static TValue? GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) 
         => dict.TryGetValue(key, out var result) ? result : defaultValue;
 
     /// <summary>

+ 1 - 1
Shared/Extensions/ExceptionExtensions.cs

@@ -44,7 +44,7 @@ public static class ExceptionExtensions {
                 lines.Add((isInnerException ? "→ " : "") + currentEx.Message.ReplaceLineEndings("\r\n", true));
                 if (currentEx.GetType() != typeof(Exception)) lines.Add("   错误类型:" + currentEx.GetType().FullName);
                 var stackLines = (currentEx.StackTrace?.ReplaceLineEndings("\r", true).Split('\r') ?? [])
-                    .Select(l => l.BeforeFirst("(") + l.AfterFirst(")"))
+                    .Select(l => l.BeforeLast("(") + l.AfterLast(")"))
                     .Distinct();
                 lines.AddRange(stackLines);
             } else {

+ 1 - 1
Shared/Extensions/StringExtensions.cs

@@ -189,7 +189,7 @@ public static class StringExtensions {
     /// 过短则用 <paramref name="code"/> 将其左侧填充,过长则截取靠左的指定长度。
     /// </summary>
     public static string EnsureLength(this string? str, char code, int length) {
-        if (str == null) str = "";
+        str ??= "";
         return str.Length > length ? str.Substring(0, length) : str.PadLeft(length, code);
     }
 

+ 6 - 2
Shared/General/Logger.cs

@@ -131,7 +131,10 @@ public class BaseLogger {
     }
     public virtual void Log(Exception ex, string? message, LogLevel level, LogBehavior behavior, string filePath) {
         if (MinLevel > level) return;
-        if (ex is ThreadInterruptedException) return;
+        if (ex is ThreadInterruptedException) {
+            Thread.CurrentThread.Interrupt();
+            return;
+        }
         try {
             var formattedMessage = (message is null ? "" : $"{message}:") + ex.GetDisplay(true);
             formattedMessage = Format(formattedMessage, level, filePath, ex);
@@ -230,7 +233,7 @@ public class FileLogger : BaseLogger, IDisposable {
                 writer = new StreamWriter(PathUtils.WithLongPath(Path.Combine(logFolder, $"{fileNamePrefix}1{fileNameSuffix}")), append: true) { AutoFlush = true };
                 writerAvailable = true;
                 Logger.Info("日志初始化成功");
-                while (true) {
+                while (writerAvailable) {
                     queuedEvent.WaitOne();
                     Flush();
                 }
@@ -246,5 +249,6 @@ public class FileLogger : BaseLogger, IDisposable {
         writerAvailable = false;
         if (writer is not null) ((IDisposable) writer).Dispose();
         queuedEvent.Dispose();
+        GC.SuppressFinalize(this);
     }
 }

+ 9 - 10
Shared/Utils/EscapeUtils.cs

@@ -6,8 +6,7 @@ public static class EscapeUtils {
     /// <summary>
     /// WPF XML 转义。
     /// </summary>
-    public static string? XmlEscape(string? str) {
-        if (str is null) return null;
+    public static string XmlEscape(string str) {
         if (str.StartsWithF("{")) str = "{}" + str; // #4187
         return str
             .Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("'", "&apos;")
@@ -34,7 +33,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="Regex.Escape"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? RegexEscape(string value) 
+    public static string RegexEscape(string value) 
         => Regex.Escape(value);
 
     /// <summary>
@@ -42,7 +41,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="Regex.Unescape"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? RegexUnescape(string value) 
+    public static string RegexUnescape(string value) 
         => Regex.Unescape(value);
 
     /// <summary>
@@ -50,7 +49,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="Uri.EscapeDataString"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? UrlEscape(string value) 
+    public static string UrlEscape(string value) 
         => Uri.EscapeDataString(value);
 
     /// <summary>
@@ -58,7 +57,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="Uri.UnescapeDataString"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? UrlUnescape(string value) 
+    public static string UrlUnescape(string value) 
         => Uri.UnescapeDataString(value);
 
     /// <summary>
@@ -66,7 +65,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="WebUtility.UrlEncode"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? FormUrlEscape(string value) 
+    public static string FormUrlEscape(string value) 
         => WebUtility.UrlEncode(value);
 
     /// <summary>
@@ -74,7 +73,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="WebUtility.UrlDecode"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? FormUrlUnescape(string value) 
+    public static string FormUrlUnescape(string value) 
         => WebUtility.UrlDecode(value);
 
     /// <summary>
@@ -82,7 +81,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="WebUtility.HtmlEncode"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? HtmlEscape(string value) 
+    public static string HtmlEscape(string value) 
         => WebUtility.HtmlEncode(value);
 
     /// <summary>
@@ -90,7 +89,7 @@ public static class EscapeUtils {
     /// 等同于 <see cref="WebUtility.HtmlDecode"/>。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public static string? HtmlUnescape(string value) 
+    public static string HtmlUnescape(string value) 
         => WebUtility.HtmlDecode(value);
 
 }

+ 4 - 2
Shared/Utils/FileUtils.cs

@@ -35,7 +35,7 @@ public static class FileUtils {
     /// 如果文件或文件夹不存在,则会自动创建。若已存在,则会覆盖原文件。
     /// </summary>
     public static void Write(string filePath, Stream stream) {
-        using FileStream fileStream = CreateAsStream(PathUtils.WithLongPath(filePath));
+        using FileStream fileStream = FileUtils.CreateAsStream(filePath);
         if (stream.CanSeek && stream.Position != 0) stream.Seek(0, SeekOrigin.Begin);
         Logger.Trace(() => $"写入文件:{filePath}({stream.GetType().Name}{(stream.CanSeek ? $" {stream.Length} 字节" : "")})");
         stream.CopyTo(fileStream);
@@ -85,7 +85,9 @@ public static class FileUtils {
             Run(target);
         } else {
             System.Runtime.ExceptionServices.ExceptionDispatchInfo? internalEx = null; // 捕获内部异常
-            var thread = new Thread(() => { try { Run(target); } catch (Exception ex) { internalEx = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex); } });
+            var thread = new Thread(
+                () => { try { Run(target); } catch (Exception ex) { internalEx = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex); } }) 
+                { IsBackground = true, Name = nameof(DeleteToRecycleBin) };
             thread.SetApartmentState(ApartmentState.STA);
             thread.Start();
             thread.Join();

+ 2 - 2
Shared/Utils/HttpUtils.cs

@@ -7,9 +7,9 @@ public class HttpRequestCodeException(string message, HttpStatusCode statusCode,
     /// <summary>
     /// HTTP 请求的状态码。
     /// </summary>
-    public HttpStatusCode StatusCode { get; set; } = statusCode;
+    public HttpStatusCode StatusCode { get; } = statusCode;
     /// <summary>
     /// 远程服务器给予的回复。
     /// </summary>
-    public string? Response { get; set; } = response;
+    public string? Response { get; } = response;
 }

+ 2 - 2
Tests/TestUtils.cs

@@ -17,7 +17,7 @@ public abstract class TestWithFolder {
             $"{GetType().Name}-{Guid.NewGuid()}",
             "文件夹 Dir_!@#$%^&()_+={}[];',_",
             new string('X', 200), new string('X', 200)) + @"\";
-        Directory.CreateDirectory(@"\\?\" + tempFolder);
+        DirectoryUtils.Create(tempFolder);
     }
 
     /// <summary>
@@ -26,7 +26,7 @@ public abstract class TestWithFolder {
     public string GetTestFile(string fileName) {
         var sourceFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", GetType().Name.Replace("Test", ""), fileName);
         var distPath = Path.Combine(tempFolder, Path.GetFileName(sourceFilePath));
-        File.Copy(sourceFilePath, @"\\?\" + distPath);
+        FileUtils.Copy(sourceFilePath, distPath);
         return distPath;
     }