浏览代码

静态 Code Review

龙腾猫跃 3 月之前
父节点
当前提交
2744437977

+ 1 - 1
Shared/Extensions/EnumerableExtensions.cs

@@ -254,7 +254,7 @@ public static class EnumerableExtensions {
                 isFirst = false;
             else
                 builder.Append(split);
-            if (element is not null) builder.Append(element.ToString());
+            if (element is not null) builder.Append(element);
         }
         return builder.ToString();
     }

+ 9 - 10
Shared/Extensions/StringExtensions.cs

@@ -140,7 +140,7 @@ public static class StringExtensions {
     /// 获取在任意子字符串第一次出现之前的部分,如果未找到任意子字符串则不裁切。
     /// <code>"2024/11-08".BeforeFirstOfAny(["/", "-"]) => "2024"</code>
     /// </summary>
-    public static string BeforeFirstOfAny(this string str, IEnumerable<string> texts, bool ignoreCase = false) {
+    public static string BeforeFirstOfAny(this string str, IEnumerable<string>? texts, bool ignoreCase = false) {
         if (texts == null) return str;
         int pos = -1;
         foreach (string text in texts) {
@@ -165,7 +165,7 @@ public static class StringExtensions {
     /// 获取在任意子字符串最后一次出现之前的部分,如果未找到任意子字符串则不裁切。
     /// <code>"2024/11-08".BeforeLastOfAny(["/", "-"]) => "2024/11"</code>
     /// </summary>
-    public static string BeforeLastOfAny(this string str, IEnumerable<string> texts, bool ignoreCase = false) {
+    public static string BeforeLastOfAny(this string str, IEnumerable<string>? texts, bool ignoreCase = false) {
         if (texts == null) return str;
         int pos = -1;
         foreach (string text in texts) {
@@ -181,8 +181,8 @@ public static class StringExtensions {
     /// 获取在子字符串第一次出现之后的部分,如果未找到子字符串则不裁切。
     /// <code>"2024/11/08".AfterFirst("/") => "11/08"</code>
     /// </summary>
-    public static string AfterFirst(this string str, string text, bool ignoreCase = false) {
-        int pos = string.IsNullOrEmpty(text) ? -1 : str.IndexOfF(text, ignoreCase);
+    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;
         return str[(pos + text!.Length)..];
     }
@@ -190,7 +190,7 @@ public static class StringExtensions {
     /// 获取在任意子字符串第一次出现之后的部分,如果未找到任意子字符串则不裁切。
     /// <code>"2024/11-08".AfterFirstOfAny(["/", "-"]) => "11-08"</code>
     /// </summary>
-    public static string AfterFirstOfAny(this string str, IEnumerable<string> texts, bool ignoreCase = false) {
+    public static string AfterFirstOfAny(this string str, IEnumerable<string>? texts, bool ignoreCase = false) {
         if (texts == null) return str;
         int pos = -1;
         int len = 0;
@@ -210,8 +210,8 @@ public static class StringExtensions {
     /// 获取在子字符串最后一次出现之后的部分,如果未找到子字符串则不裁切。
     /// <code>"2024/11/08".AfterLast("/") => "08"</code>
     /// </summary>
-    public static string AfterLast(this string str, string text, bool ignoreCase = false) {
-        int pos = string.IsNullOrEmpty(text) ? -1 : str.LastIndexOfF(text, ignoreCase);
+    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;
         return str[(pos + text!.Length)..];
     }
@@ -219,7 +219,7 @@ public static class StringExtensions {
     /// 获取在任意子字符串最后一次出现之后的部分,如果未找到任意子字符串则不裁切。
     /// <code>"2024/11-08".AfterLastOfAny(["/", "-"]) => "08"</code>
     /// </summary>
-    public static string AfterLastOfAny(this string str, IEnumerable<string> texts, bool ignoreCase = false) {
+    public static string AfterLastOfAny(this string str, IEnumerable<string>? texts, bool ignoreCase = false) {
         if (texts == null) return str;
         int pos = -1;
         int len = 0;
@@ -413,8 +413,7 @@ public static class StringExtensions {
             throw new FormatException("JSON deserialize failed: " + 
                 (length > 2000
                     ? $"{data![..500]}...(total {length} characters)...{data[^500..]}"
-                    : (data ?? "")), 
-            ex);
+                    : (data ?? "")), ex);
         }
     }
     /// <summary>

+ 1 - 1
Shared/General/ConcurrentList.cs

@@ -4,7 +4,7 @@ namespace MeloongCore;
 /// 使用粗粒度锁的线程安全 <see cref="List{T}"/> 实现。
 /// 通常建议使用 <see cref="ConcurrentBag{T}"/> 或其他线程安全集合,它们具有更好的性能。
 /// </summary>
-public class ConcurrentList<T> : IList<T>, IList, IEnumerable, IEnumerable<T> {
+public class ConcurrentList<T> : IList<T>, IList {
 
     private readonly List<T> _items = [];
 

+ 4 - 2
Shared/General/Config.cs

@@ -14,7 +14,9 @@ public class JsonConfigProvider : IConfigProvider {
     public static readonly ConcurrentBag<WeakReference<JsonConfigProvider>> allProviders = [];
     private readonly string filePath;
 
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。
     public JsonConfigProvider(string filePath) {
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。
         this.filePath = filePath;
         allProviders.Add(new(this));
         saveAction = Throttler.Throttle(Save, TimeSpan.FromMilliseconds(500), leading: false, trailing: true);
@@ -48,8 +50,8 @@ public class JsonConfigProvider : IConfigProvider {
             } else if (encrypted) {
                 // 需要加密,在 JSON 中保存密文字符串
                 throw new NotImplementedException("加密需要改为使用识别码,现在尚未实现。");
-                Logger.Trace($"配置已修改:{key} = 已加密({filePath})");
-                json.Value[key] = CryptographyUtils.AesEncrypt(value is string str ? str : JsonConvert.SerializeObject(value));
+                // Logger.Trace($"配置已修改:{key} = 已加密({filePath})");
+                // json.Value[key] = CryptographyUtils.AesEncrypt(value is string str ? str : JsonConvert.SerializeObject(value));
             } else {
                 // 用 JToken 保留原始结构
                 JToken token = JToken.FromObject(value);

+ 3 - 3
Shared/General/Logger.cs

@@ -122,12 +122,12 @@ public static class Logger {
     public ref struct LogInterpolatedStringHandler {
         private StringBuilder? _sb;
         public readonly bool isEnabled;
-        public LogInterpolatedStringHandler(int literalLength, int formattedCount, LogLevel level, out bool isEnabled) {
+        public LogInterpolatedStringHandler(int literalLength, LogLevel level, out bool isEnabled) {
             this.isEnabled = isEnabled = Logger.Instance.MinLevel <= level;
             _sb = this.isEnabled ? new StringBuilder(literalLength) : null;
         }
         public void AppendLiteral(string value) => _sb?.Append(value);
-        public void AppendFormatted<T>(T value) => _sb?.Append(value?.ToString());
+        public void AppendFormatted<T>(T value) => _sb?.Append(value);
         public void AppendFormatted<T>(T value, string? format) => _sb?.Append(value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString());
         public void AppendFormatted<T>(T value, int alignment) {
             if (_sb is null) return;
@@ -156,7 +156,7 @@ public static class Logger {
     public ref struct LogInterpolatedStringHandler<TLevel> where TLevel : struct, ILogLevelProvider {
         private LogInterpolatedStringHandler _inner;
         public bool IsEnabled => _inner.isEnabled;
-        public LogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) => _inner = new(literalLength, formattedCount, default(TLevel).Level, out isEnabled);
+        public LogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) => _inner = new(literalLength, default(TLevel).Level, out isEnabled);
         public void AppendLiteral(string value) => _inner.AppendLiteral(value);
         public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
         public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);

+ 8 - 8
Shared/General/ValueRange.cs

@@ -7,7 +7,7 @@ public sealed class ValueRange<T>(
     T? lower, T? upper,
     bool isLowerInclusive, bool isUpperInclusive,
     bool hasLower, bool hasUpper
-) : IEquatable<ValueRange<T>?> where T : notnull, IComparable<T> {
+) : IEquatable<ValueRange<T>?> where T : IComparable<T> {
 
     #region 基础字段
 
@@ -129,7 +129,7 @@ public sealed class ValueRange<T>(
     /// 取当前范围与另一个范围的交集。
     /// </summary>
     /// <returns> 若两个范围存在交集,则返回交集范围;否则返回 <see langword="null" />。 </returns>
-    public ValueRange<T>? Intersect(ValueRange<T> other) {
+    public ValueRange<T> Intersect(ValueRange<T> other) {
         T? lower = default;
         T? upper = default;
         var isLowerInclusive = true;
@@ -200,12 +200,12 @@ public sealed class ValueRange<T>(
     }
     public override int GetHashCode() {
         int hashCode = 169504101;
-        hashCode=hashCode*-1521134295+EqualityComparer<T?>.Default.GetHashCode(Lower);
-        hashCode=hashCode*-1521134295+EqualityComparer<T?>.Default.GetHashCode(Upper);
-        hashCode=hashCode*-1521134295+HasLower.GetHashCode();
-        hashCode=hashCode*-1521134295+HasUpper.GetHashCode();
-        hashCode=hashCode*-1521134295+IsLowerInclusive.GetHashCode();
-        hashCode=hashCode*-1521134295+IsUpperInclusive.GetHashCode();
+        hashCode=hashCode*-1521134295+EqualityComparer<T?>.Default.GetHashCode(lower);
+        hashCode=hashCode*-1521134295+EqualityComparer<T?>.Default.GetHashCode(upper);
+        hashCode=hashCode*-1521134295+hasLower.GetHashCode();
+        hashCode=hashCode*-1521134295+hasUpper.GetHashCode();
+        hashCode=hashCode*-1521134295+isLowerInclusive.GetHashCode();
+        hashCode=hashCode*-1521134295+isUpperInclusive.GetHashCode();
         return hashCode;
     }
 #pragma warning disable CS8604 // 引用类型参数可能为 null。

+ 1 - 1
Shared/General/Workers/RateLimitedWorker.cs

@@ -18,7 +18,7 @@ public class RateLimitedWorker(Action workload, double minimalIntervalMs, RateLi
     private readonly RateLimitedAction action = mode switch {
         RateLimitMode.ImmediateThenMerge => Throttler.Throttle(workload, TimeSpan.FromMilliseconds(minimalIntervalMs), leading: true, trailing: true),
         RateLimitMode.WaitUnillNoNewInvoke => Debouncer.Debounce(workload, TimeSpan.FromMilliseconds(minimalIntervalMs), leading: false, trailing: true),
-        RateLimitMode.ImmediateThenIgnore => Debouncer.Debounce(workload, TimeSpan.FromMilliseconds(minimalIntervalMs), leading: true, trailing: false)
+        _ => Debouncer.Debounce(workload, TimeSpan.FromMilliseconds(minimalIntervalMs), leading: true, trailing: false)
     };
 
     public void Start() {

+ 4 - 4
Shared/Utils/DirectoryUtils.cs

@@ -68,10 +68,10 @@ public static class DirectoryUtils {
     public static void Copy(string sourceFolder, string destFolder) {
         sourceFolder = PathUtils.ForCompare(sourceFolder);
         destFolder = PathUtils.ForCompare(destFolder);
-        if (string.Compare(sourceFolder, destFolder, ignoreCase: false) == 0) {
+        if (string.CompareOrdinal(sourceFolder, destFolder) == 0) {
             // 复制自身到自身,则不执行操作
             Logger.Trace($"复制文件夹到自身,不执行操作:{sourceFolder} → {destFolder}");
-        } else if (string.Compare(sourceFolder, destFolder, ignoreCase: true) == 0) {
+        } else if (string.Compare(sourceFolder, destFolder, StringComparison.OrdinalIgnoreCase) == 0) {
             // 路径仅大小写不同,等效于重命名
             Logger.Trace($"复制文件夹到自身,但大小写不同,等效于重命名文件夹:{sourceFolder} → {destFolder}");
             DirectoryUtils.Move(sourceFolder, destFolder);
@@ -90,10 +90,10 @@ public static class DirectoryUtils {
         SafetyCheck(sourceFolder);
         sourceFolder = PathUtils.ForCompare(sourceFolder);
         destFolder = PathUtils.ForCompare(destFolder);
-        if (string.Compare(sourceFolder, destFolder, ignoreCase: false) == 0) {
+        if (string.CompareOrdinal(sourceFolder, destFolder) == 0) {
             // 剪切自身到自身,则不执行操作
             Logger.Trace($"剪切文件夹到自身,不执行操作:{sourceFolder} → {destFolder}");
-        } else if (string.Compare(sourceFolder, destFolder, ignoreCase: true) == 0) {
+        } else if (string.Compare(sourceFolder, destFolder, StringComparison.OrdinalIgnoreCase) == 0) {
             // 路径仅大小写不同
             Logger.Trace($"剪切文件夹到自身,但大小写不同:{sourceFolder} → {destFolder}");
             var temp = Path.Combine(PathUtils.RemoveLastPart(sourceFolder), Path.GetRandomFileName());

+ 5 - 4
Shared/Utils/FileUtils.cs

@@ -151,10 +151,10 @@ public static class FileUtils {
     public static void Copy(string sourceFilePath, string destFilePath) {
         sourceFilePath = PathUtils.ForCompare(sourceFilePath);
         destFilePath = PathUtils.ForCompare(destFilePath);
-        if (String.CompareOrdinal(sourceFilePath, destFilePath) == 0) {
+        if (string.CompareOrdinal(sourceFilePath, destFilePath) == 0) {
             // 复制自身到自身,则不执行操作
             Logger.Trace($"复制文件到自身,不执行操作:{sourceFilePath} → {destFilePath}");
-        } else if (String.Compare(sourceFilePath, destFilePath, StringComparison.OrdinalIgnoreCase) == 0) {
+        } else if (string.Compare(sourceFilePath, destFilePath, StringComparison.OrdinalIgnoreCase) == 0) {
             // 路径仅大小写不同,等效于重命名
             Logger.Trace($"复制文件到自身,但大小写不同,等效于重命名文件:{sourceFilePath} → {destFilePath}");
             FileUtils.Move(sourceFilePath, destFilePath);
@@ -175,10 +175,10 @@ public static class FileUtils {
     public static void Move(string sourceFilePath, string destFilePath) {
         sourceFilePath = PathUtils.ForCompare(sourceFilePath);
         destFilePath = PathUtils.ForCompare(destFilePath);
-        if (String.CompareOrdinal(sourceFilePath, destFilePath) == 0) {
+        if (string.CompareOrdinal(sourceFilePath, destFilePath) == 0) {
             // 剪切自身到自身,则不执行操作
             Logger.Trace($"剪切文件到自身,不执行操作:{sourceFilePath} → {destFilePath}");
-        } else if (String.Compare(sourceFilePath, destFilePath, StringComparison.OrdinalIgnoreCase) == 0) {
+        } else if (string.Compare(sourceFilePath, destFilePath, StringComparison.OrdinalIgnoreCase) == 0) {
             // 路径仅大小写不同
             Logger.Trace($"剪切文件到自身,但大小写不同:{sourceFilePath} → {destFilePath}");
             Retrier.Attempt(delay: _ => TimeSpan.FromMilliseconds(200), isRetryAllowed: ex => ex is IOException, action: _ => {
@@ -245,6 +245,7 @@ public static class FileUtils {
             try {
                 var iid = typeof(IShellItem).GUID;
                 Marshal.ThrowExceptionForHR(SHCreateItemFromParsingName(PathUtils.RemoveExtendedPrefix(target), IntPtr.Zero, ref iid, out item));
+                // ReSharper disable once SuspiciousTypeConversion.Global
                 op = (IFileOperation) new FileOperation();
                 op.SetOperationFlags(0x0040 | 0x0010 | 0x0004); // FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT
                 op.DeleteItem(item, IntPtr.Zero);

+ 4 - 4
Shared/Utils/PathUtils.cs

@@ -38,7 +38,7 @@ public static class PathUtils {
         }
         // 逐级向上寻找已存在的文件夹,将不存在的部分挪到 suffix,不再缩短
         while (!DirectoryUtils.Exists(pathToShorten) && !FileUtils.Exists(pathToShorten)) { // 如果路径不存在
-            string parentPath = Path.GetDirectoryName(pathToShorten);
+            string? parentPath = Path.GetDirectoryName(pathToShorten);
             if (string.IsNullOrEmpty(parentPath) || parentPath == pathToShorten) return pathName; // 已经到达根目录,全都不存在,直接返回
             pathToKeep = Path.Combine(PathUtils.GetLastPart(pathToShorten), pathToKeep);
             pathToShorten = parentPath;
@@ -93,10 +93,10 @@ public static class PathUtils {
     /// "https://foo.bar/file/pack.zip?arg=1" → "https://foo.bar/file"
     /// </code></summary>
     public static string RemoveLastPart(string pathName) {
-        if (pathName!.Contains("://")) { // 网络路径
+        if (pathName.Contains("://")) { // 网络路径
             pathName = PathUtils.RemoveSlashSuffix(pathName.BeforeFirst("#").BeforeFirst("?")); // 去除参数
         }
-        return PathUtils.RemoveSlashSuffix(pathName!).BeforeLastOfAny([@"\", "/"]);
+        return PathUtils.RemoveSlashSuffix(pathName).BeforeLastOfAny([@"\", "/"]);
     }
 
     /// <summary>
@@ -108,7 +108,7 @@ public static class PathUtils {
     /// "https://foo.bar/file/pack.zip?arg=1" → "pack.zip"
     /// </code></summary>
     public static string GetLastPart(string pathName) {
-        if (pathName!.Contains("://")) { // 网络路径
+        if (pathName.Contains("://")) { // 网络路径
             pathName = PathUtils.RemoveSlashSuffix(pathName.BeforeFirst("#").BeforeFirst("?")); // 去除参数
         } else { // 文件路径
             pathName = PathUtils.RemoveSlashSuffix(pathName);

+ 8 - 7
Shared/Utils/StringUtils.cs

@@ -1,3 +1,4 @@
+using System.Globalization;
 using System.Text.RegularExpressions;
 
 namespace MeloongCore;
@@ -68,7 +69,7 @@ public static class StringUtils {
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static string FormUrlEscape(string value) 
-        => WebUtility.UrlEncode(value);
+        => WebUtility.UrlEncode(value)!;
 
     /// <summary>
     /// 表单去转义。
@@ -107,14 +108,14 @@ public static class StringUtils {
         if (byteSize < 1000) { // B 级
             return $"{sign}{byteSize} B";
         } else if (byteSize < 1024 * 1000) { // K 级
-            var roundResult = Math.Round(byteSize / 1024D).ToString();
-            return $"{sign}{Math.Round(byteSize / 1024D, (int) (3 - roundResult.Length).Clamp(0, 2))} K";
+            var roundResult = Math.Round(byteSize / 1024D).ToString(CultureInfo.InvariantCulture);
+            return $"{sign}{Math.Round(byteSize / 1024D, (3 - roundResult.Length).Clamp(0, 2))} K";
         } else if (byteSize < 1024 * 1024 * 1000) { // M 级
-            var roundResult = Math.Round(byteSize / 1024D / 1024D).ToString();
-            return $"{sign}{Math.Round(byteSize / 1024D / 1024D, (int) (3 - roundResult.Length).Clamp(0, 2))} M";
+            var roundResult = Math.Round(byteSize / 1024D / 1024D).ToString(CultureInfo.InvariantCulture);
+            return $"{sign}{Math.Round(byteSize / 1024D / 1024D, (3 - roundResult.Length).Clamp(0, 2))} M";
         } else { // G 级
-            var roundResult = Math.Round(byteSize / 1024D / 1024D / 1024D).ToString();
-            return $"{sign}{Math.Round(byteSize / 1024D / 1024D / 1024D, (int) (3 - roundResult.Length).Clamp(0, 2))} G";
+            var roundResult = Math.Round(byteSize / 1024D / 1024D / 1024D).ToString(CultureInfo.InvariantCulture);
+            return $"{sign}{Math.Round(byteSize / 1024D / 1024D / 1024D, (3 - roundResult.Length).Clamp(0, 2))} G";
         }
     }
 

+ 5 - 30
Shared/Utils/TaskUtils.cs

@@ -14,7 +14,7 @@ public static class TaskUtils {
         }
         // 实际分割
         var progressEach = 1d / taskList.Count;
-        return Task.WhenAll(taskList.Select(async (task, index) => {
+        return Task.WhenAll(taskList.Select(async (task, _) => {
             var cp = progress?.SplitBy(progressEach)[0];
             await task(cp).NoCapture();
             cp?.Finish();
@@ -31,7 +31,7 @@ public static class TaskUtils {
         }
         // 实际分割
         var progressEach = 1d / taskList.Count;
-        return Task.WhenAll(taskList.Select(async (task, index) => {
+        return Task.WhenAll(taskList.Select(async (task, _) => {
             var cp = progress?.SplitBy(progressEach)[0];
             var result = await task(cp).NoCapture();
             cp?.Finish();
@@ -51,33 +51,6 @@ public static class TaskUtils {
 
     public static void ForEach<T>(IEnumerable<T> source, Action<T> body)
         => Parallel.ForEach(source, body);
-    public static async Task ForEachAsync<T>(IEnumerable<T> source, int maxDegreeOfParallelism, Func<T, ProgressProvider?, Task> body,
-        CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
-        using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-        var token = linkedCancellationTokenSource.Token;
-        var enumeratorLock = new object();
-        using var enumerator = source.GetEnumerator();
-        async Task WorkerAsync(ProgressProvider? cp) {
-            try {
-                while (true) {
-                    T item;
-                    lock (enumeratorLock) {
-                        token.ThrowIfCancellationRequested();
-                        if (!enumerator.MoveNext()) return;
-                        item = enumerator.Current;
-                    }
-                    await body(item, cp).NoCapture();
-                }
-            } catch {
-                try {
-                    linkedCancellationTokenSource.Cancel(); // 让尚未开始的 worker 尽快停止
-                } catch {
-                }
-                throw;
-            }
-        }
-        await TaskUtils.WhenAll(Enumerable.Repeat(WorkerAsync, maxDegreeOfParallelism), progress).NoCapture();
-    }
 
     /// <summary>
     /// 静默运行程序并等待其结束,返回其输出和退出码。
@@ -94,7 +67,9 @@ public static class TaskUtils {
             RedirectStandardOutput = true, StandardOutputEncoding = encoding,
             RedirectStandardError = true, StandardErrorEncoding = encoding
         };
-        using var program = new Process { StartInfo = info, EnableRaisingEvents = true };
+        using var program = new Process();
+        program.StartInfo = info;
+        program.EnableRaisingEvents = true;
         if (!program.Start()) throw new InvalidOperationException($"运行程序时出现意外错误:{file} {arguments}");
         bool hasTimeout = timeoutMs is > 0;
         Logger.Info($"运行程序,并返回其输出:{file} {arguments}{(hasTimeout ? $",最长可等待 {timeoutMs}ms" : "")}");

+ 1 - 1
Wpf/Controls/ModernTooltipService.cs

@@ -326,7 +326,7 @@ public static class ModernTooltipService {
             };
         }
 
-        popup!.PlacementTarget = owner;
+        popup.PlacementTarget = owner;
         UpdatePosition(owner, point);
         ObserveToolTip(owner);
         UpdateContent(owner, content!, sourceToolTip);