This commit is contained in:
Lightczx
2023-07-30 12:47:22 +08:00
parent c5ab707b66
commit 93ee1a3386
29 changed files with 112 additions and 57 deletions

View File

@@ -138,6 +138,12 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator
return boolValue ? "true" : "false";
}
return typedConstant.Value!.ToString();
string result = typedConstant.Value!.ToString();
if (string.IsNullOrEmpty(result))
{
return default;
}
return result;
}
}

View File

@@ -40,7 +40,7 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
context.RegisterCompilationStartAction(CompilationStart);
}
private void CompilationStart(CompilationStartAnalysisContext context)
private static void CompilationStart(CompilationStartAnalysisContext context)
{
SyntaxKind[] types = new SyntaxKind[]
{
@@ -50,13 +50,14 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
SyntaxKind.EnumDeclaration
};
context.RegisterSyntaxNodeAction(HandleTypeDeclaration, types);
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
context.RegisterSyntaxNodeAction(HandleMethodDeclaration, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleConstructorDeclaration, SyntaxKind.ConstructorDeclaration);
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
}
private void HandleTypeDeclaration(SyntaxNodeAnalysisContext context)
private static void HandleTypeShouldBeInternal(SyntaxNodeAnalysisContext context)
{
BaseTypeDeclarationSyntax syntax = (BaseTypeDeclarationSyntax)context.Node;
@@ -90,15 +91,31 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
}
}
private void HandleMethodDeclaration(SyntaxNodeAnalysisContext context)
private static void HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask(SyntaxNodeAnalysisContext context)
{
MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node;
IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!;
// 跳过重载方法
if (methodSyntax.Modifiers.Any(token => token.IsKind(SyntaxKind.OverrideKeyword)))
{
return;
}
if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task"))
{
Location location = methodSyntax.ReturnType.GetLocation();
Diagnostic diagnostic = Diagnostic.Create(useValueTaskIfPossibleDescriptor, location);
context.ReportDiagnostic(diagnostic);
}
}
private static void HandleMethodParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context)
{
MethodDeclarationSyntax methodSyntax = (MethodDeclarationSyntax)context.Node;
IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax)!;
if (methodSymbol.ReturnType.IsOrInheritsFrom("System.Threading.Tasks.Task"))
{
return;
}
@@ -150,7 +167,7 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
}
}
private void HandleConstructorDeclaration(SyntaxNodeAnalysisContext context)
private static void HandleConstructorParameterShouldUseRefLikeKeyword(SyntaxNodeAnalysisContext context)
{
ConstructorDeclarationSyntax constructorSyntax = (ConstructorDeclarationSyntax)context.Node;

View File

@@ -143,7 +143,7 @@ internal struct Rgba32
/// 转换到 HSL 颜色
/// </summary>
/// <returns>HSL 颜色</returns>
public HslColor ToHsl()
public readonly HslColor ToHsl()
{
const double toDouble = 1.0 / 255;
double r = toDouble * R;

View File

@@ -54,7 +54,7 @@ internal class ScopedPage : Page
/// </summary>
/// <param name="extra">额外内容</param>
/// <returns>任务</returns>
public async Task NotifyRecipientAsync(INavigationData extra)
public async ValueTask NotifyRecipientAsync(INavigationData extra)
{
if (extra.Data != null && DataContext is INavigationRecipient recipient)
{

View File

@@ -17,7 +17,7 @@ internal interface IImageCache : ICastService
/// </summary>
/// <param name="uri">Uri of the item.</param>
/// <returns>a string path</returns>
Task<ValueFile> GetFileFromCacheAsync(Uri uri);
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri);
/// <summary>
/// Removed items based on uri list passed

View File

@@ -93,7 +93,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
}
/// <inheritdoc/>
public async Task<ValueFile> GetFileFromCacheAsync(Uri uri)
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
{
string fileName = GetCacheFileName(uri);
string filePath = Path.Combine(GetCacheFolder(), fileName);
@@ -166,6 +166,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
}
}
[SuppressMessage("", "SH003")]
private async Task DownloadFileAsync(Uri uri, string baseFile)
{
logger.LogInformation("Begin downloading for {Uri}", uri);

View File

@@ -35,8 +35,8 @@ internal static class QueryableExtension
/// <param name="token">取消令牌</param>
/// <returns>SQL返回个数</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
public static ValueTask<int> ExecuteDeleteWhereAsync<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, CancellationToken token = default)
{
return source.Where(predicate).ExecuteDeleteAsync(token);
return source.Where(predicate).ExecuteDeleteAsync(token).AsValueTask();
}
}

View File

@@ -17,7 +17,7 @@ internal sealed partial class ClipboardInterop : IClipboardInterop
private readonly ITaskContext taskContext;
/// <inheritdoc/>
public async Task<T?> DeserializeFromJsonAsync<T>()
public async ValueTask<T?> DeserializeFromJsonAsync<T>()
where T : class
{
await taskContext.SwitchToMainThreadAsync();

View File

@@ -15,7 +15,7 @@ internal interface IClipboardInterop
/// </summary>
/// <typeparam name="T">目标类型</typeparam>
/// <returns>实例</returns>
Task<T?> DeserializeFromJsonAsync<T>()
ValueTask<T?> DeserializeFromJsonAsync<T>()
where T : class;
/// <summary>

View File

@@ -17,7 +17,7 @@ internal static class MD5
/// <param name="filePath">文件路径</param>
/// <param name="token">取消令牌</param>
/// <returns>文件 Md5 摘要</returns>
public static async Task<string> HashFileAsync(string filePath, CancellationToken token = default)
public static async ValueTask<string> HashFileAsync(string filePath, CancellationToken token = default)
{
await using (FileStream stream = File.OpenRead(filePath))
{
@@ -31,7 +31,7 @@ internal static class MD5
/// <param name="stream">流</param>
/// <param name="token">取消令牌</param>
/// <returns>流 Md5 摘要</returns>
public static async Task<string> HashAsync(Stream stream, CancellationToken token = default)
public static async ValueTask<string> HashAsync(Stream stream, CancellationToken token = default)
{
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{

View File

@@ -17,7 +17,7 @@ internal static class XXH64
/// <param name="stream">流</param>
/// <param name="token">取消令牌</param>
/// <returns>摘要</returns>
public static async Task<string> HashAsync(Stream stream, CancellationToken token = default)
public static async ValueTask<string> HashAsync(Stream stream, CancellationToken token = default)
{
XxHash64 xxHash64 = new();
await xxHash64.AppendAsync(stream, token).ConfigureAwait(false);
@@ -31,7 +31,7 @@ internal static class XXH64
/// <param name="path">路径</param>
/// <param name="token">取消令牌</param>
/// <returns>摘要</returns>
public static async Task<string> HashFileAsync(string path, CancellationToken token = default)
public static async ValueTask<string> HashFileAsync(string path, CancellationToken token = default)
{
await using (FileStream stream = File.OpenRead(path))
{

View File

@@ -13,7 +13,7 @@ namespace Snap.Hutao.Core.IO;
internal static class PickerExtension
{
/// <inheritdoc cref="FileOpenPicker.PickSingleFileAsync"/>
public static async Task<ValueResult<bool, ValueFile>> TryPickSingleFileAsync(this FileOpenPicker picker)
public static async ValueTask<ValueResult<bool, ValueFile>> TryPickSingleFileAsync(this FileOpenPicker picker)
{
StorageFile? file;
Exception? exception = null;
@@ -39,7 +39,7 @@ internal static class PickerExtension
}
/// <inheritdoc cref="FileSavePicker.PickSaveFileAsync"/>
public static async Task<ValueResult<bool, ValueFile>> TryPickSaveFileAsync(this FileSavePicker picker)
public static async ValueTask<ValueResult<bool, ValueFile>> TryPickSaveFileAsync(this FileSavePicker picker)
{
StorageFile? file;
Exception? exception = null;
@@ -65,7 +65,7 @@ internal static class PickerExtension
}
/// <inheritdoc cref="FolderPicker.PickSingleFolderAsync"/>
public static async Task<ValueResult<bool, string>> TryPickSingleFolderAsync(this FolderPicker picker)
public static async ValueTask<ValueResult<bool, string>> TryPickSingleFolderAsync(this FolderPicker picker)
{
StorageFolder? folder;
Exception? exception = null;

View File

@@ -38,7 +38,7 @@ internal sealed class StreamCopyWorker
/// </summary>
/// <param name="progress">进度</param>
/// <returns>任务</returns>
public async Task CopyAsync(IProgress<StreamCopyStatus> progress)
public async ValueTask CopyAsync(IProgress<StreamCopyStatus> progress)
{
long totalBytesRead = 0;
int bytesRead;
@@ -91,7 +91,7 @@ internal sealed class StreamCopyWorker<TStatus>
/// </summary>
/// <param name="progress">进度</param>
/// <returns>任务</returns>
public async Task CopyAsync(IProgress<TStatus> progress)
public async ValueTask CopyAsync(IProgress<TStatus> progress)
{
long totalBytesRead = 0;
int bytesRead;

View File

@@ -37,7 +37,7 @@ internal readonly struct ValueFile
/// <typeparam name="T">内容的类型</typeparam>
/// <param name="options">序列化选项</param>
/// <returns>操作是否成功,反序列化后的内容</returns>
public async Task<ValueResult<bool, T?>> DeserializeFromJsonAsync<T>(JsonSerializerOptions options)
public async ValueTask<ValueResult<bool, T?>> DeserializeFromJsonAsync<T>(JsonSerializerOptions options)
where T : class
{
try
@@ -62,7 +62,7 @@ internal readonly struct ValueFile
/// <param name="obj">对象</param>
/// <param name="options">序列化选项</param>
/// <returns>操作是否成功</returns>
public async Task<bool> SerializeToJsonAsync<T>(T obj, JsonSerializerOptions options)
public async ValueTask<bool> SerializeToJsonAsync<T>(T obj, JsonSerializerOptions options)
{
try
{

View File

@@ -103,7 +103,7 @@ internal sealed class Activation : IActivation
}
}
private async Task HandleActivationAsync(AppActivationArguments args, bool isRedirected)
private async ValueTask HandleActivationAsync(AppActivationArguments args, bool isRedirected)
{
if (activateSemaphore.CurrentCount > 0)
{
@@ -114,7 +114,7 @@ internal sealed class Activation : IActivation
}
}
private async Task HandleActivationCoreAsync(AppActivationArguments args, bool isRedirected)
private async ValueTask HandleActivationCoreAsync(AppActivationArguments args, bool isRedirected)
{
if (args.Kind == ExtendedActivationKind.Protocol)
{
@@ -145,7 +145,7 @@ internal sealed class Activation : IActivation
}
}
private async Task HandleNormalLaunchActionAsync()
private async ValueTask HandleNormalLaunchActionAsync()
{
// Increase launch times
LocalSetting.Set(SettingKeys.LaunchTimes, LocalSetting.Get(SettingKeys.LaunchTimes, 0) + 1);
@@ -161,7 +161,7 @@ internal sealed class Activation : IActivation
}
}
private async Task WaitMainWindowAsync()
private async ValueTask WaitMainWindowAsync()
{
if (!mainWindowReference.TryGetTarget(out _))
{
@@ -183,7 +183,7 @@ internal sealed class Activation : IActivation
}
}
private async Task HandleUrlActivationAsync(Uri uri, bool isRedirected)
private async ValueTask HandleUrlActivationAsync(Uri uri, bool isRedirected)
{
UriBuilder builder = new(uri);
@@ -214,7 +214,7 @@ internal sealed class Activation : IActivation
}
}
private async Task HandleAchievementActionAsync(string action, string parameter, bool isRedirected)
private async ValueTask HandleAchievementActionAsync(string action, string parameter, bool isRedirected)
{
_ = parameter;
_ = isRedirected;
@@ -234,7 +234,7 @@ internal sealed class Activation : IActivation
}
}
private async Task HandleDailyNoteActionAsync(string action, string parameter, bool isRedirected)
private async ValueTask HandleDailyNoteActionAsync(string action, string parameter, bool isRedirected)
{
_ = parameter;
switch (action)
@@ -258,7 +258,7 @@ internal sealed class Activation : IActivation
}
}
private async Task HandleLaunchGameActionAsync(string? uid = null)
private async ValueTask HandleLaunchGameActionAsync(string? uid = null)
{
serviceProvider
.GetRequiredService<IMemoryCache>()

View File

@@ -36,6 +36,7 @@ internal sealed class DebugLogger : ILogger
}
/// <inheritdoc />
[SuppressMessage("", "SH002")]
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))

View File

@@ -7,7 +7,6 @@ namespace Snap.Hutao.Core.Setting;
/// 设置键
/// </summary>
[HighQuality]
[SuppressMessage("", "SA1124")]
internal static class SettingKeys
{
/// <summary>

View File

@@ -12,5 +12,5 @@ internal interface IJumpListInterop
/// 异步配置跳转列表
/// </summary>
/// <returns>任务</returns>
Task ConfigureAsync();
ValueTask ConfigureAsync();
}

View File

@@ -17,7 +17,7 @@ internal sealed class JumpListInterop : IJumpListInterop
/// 异步配置跳转列表
/// </summary>
/// <returns>任务</returns>
public async Task ConfigureAsync()
public async ValueTask ConfigureAsync()
{
if (JumpList.IsSupported())
{

View File

@@ -38,7 +38,7 @@ internal class AsyncBarrier
/// that completes when all other participants have also signaled ready.
/// </summary>
/// <returns>A Task, which will complete (or may already be completed) when the last participant calls this method.</returns>
public Task SignalAndWaitAsync()
public ValueTask SignalAndWaitAsync()
{
lock (waiters)
{
@@ -52,14 +52,14 @@ internal class AsyncBarrier
}
// And allow this one to continue immediately.
return Task.CompletedTask;
return ValueTask.CompletedTask;
}
else
{
// We need more folks. So suspend this caller.
TaskCompletionSource tcs = new();
waiters.Enqueue(tcs);
return tcs.Task;
return tcs.Task.AsValueTask();
}
}
}

View File

@@ -11,8 +11,8 @@ internal readonly struct Delay
/// <param name="minMilliSeconds">最小,闭</param>
/// <param name="maxMilliSeconds">最小,开</param>
/// <returns>任务</returns>
public static Task RandomAsync(int minMilliSeconds, int maxMilliSeconds)
public static ValueTask RandomAsync(int minMilliSeconds, int maxMilliSeconds)
{
return Task.Delay((int)(System.Random.Shared.NextDouble() * (maxMilliSeconds - minMilliSeconds)) + minMilliSeconds);
return Task.Delay((int)(Random.Shared.NextDouble() * (maxMilliSeconds - minMilliSeconds)) + minMilliSeconds).AsValueTask();
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading;
@@ -13,6 +14,20 @@ namespace Snap.Hutao.Core.Threading;
[SuppressMessage("", "VSTHRD100")]
internal static class TaskExtension
{
[SuppressMessage("", "VSTHRD200")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueTask AsValueTask(this Task task)
{
return new(task);
}
[SuppressMessage("", "VSTHRD200")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueTask<T> AsValueTask<T>(this Task<T> task)
{
return new(task);
}
/// <summary>
/// 安全的触发任务
/// </summary>

View File

@@ -31,7 +31,9 @@ internal static partial class EnumerableExtension
/// <typeparam name="TSource">源的类型</typeparam>
/// <param name="source">源</param>
/// <returns>集合</returns>
#if NET8_0
[Obsolete("Use C# 12 Collection Literal instead")]
#endif
public static IEnumerable<TSource> Enumerate<TSource>(this TSource source)
{
yield return source;

View File

@@ -16,7 +16,9 @@ internal static class ObjectExtension
/// <typeparam name="T">数据类型</typeparam>
/// <param name="source">源</param>
/// <returns>数组</returns>
[Obsolete("Use C# 12 Collection Literals when we migrate")]
#if NET8_0
[Obsolete("Use C# 12 Collection Literals")]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] ToArray<T>(this T source)
{

View File

@@ -32,4 +32,13 @@ internal sealed class UIIF
/// </summary>
[JsonPropertyName("list")]
public List<UIIFItem> List { get; set; } = default!;
/// <summary>
/// 确认当前UIIF对象的版本是否受支持
/// </summary>
/// <returns>当前UIIF对象是否受支持</returns>
public bool IsCurrentVersionSupported()
{
return SupportedVersion.Contains(Info?.UIIFVersion ?? string.Empty);
}
}

View File

@@ -35,7 +35,7 @@ internal sealed partial class PackageConverter
/// <param name="gameFolder">游戏目录</param>
/// <param name="progress">进度</param>
/// <returns>替换结果与资源</returns>
public async Task<bool> EnsureGameResourceAsync(LaunchScheme targetScheme, GameResource gameResource, string gameFolder, IProgress<PackageReplaceStatus> progress)
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme targetScheme, GameResource gameResource, string gameFolder, IProgress<PackageReplaceStatus> progress)
{
string scatteredFilesUrl = gameResource.Game.Latest.DecompressedPath;
Uri pkgVersionUri = $"{scatteredFilesUrl}/pkg_version".ToUri();
@@ -56,7 +56,7 @@ internal sealed partial class PackageConverter
/// <param name="resource">游戏资源</param>
/// <param name="gameFolder">游戏文件夹</param>
/// <returns>任务</returns>
public async Task EnsureDeprecatedFilesAndSdkAsync(GameResource resource, string gameFolder)
public async ValueTask EnsureDeprecatedFilesAndSdkAsync(GameResource resource, string gameFolder)
{
string sdkDllBackup = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll.backup");
string sdkDll = Path.Combine(gameFolder, YuanShenData, "Plugins\\PCGameSDK.dll");
@@ -164,7 +164,7 @@ internal sealed partial class PackageConverter
File.Move(targetFullPath, cacheFilePath, true);
}
private async Task<Dictionary<string, VersionItem>> TryGetLocalItemsAsync(string gameFolder, ConvertDirection direction)
private async ValueTask<Dictionary<string, VersionItem>> TryGetLocalItemsAsync(string gameFolder, ConvertDirection direction)
{
using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, "pkg_version")))
{
@@ -172,7 +172,7 @@ internal sealed partial class PackageConverter
}
}
private async Task<Dictionary<string, VersionItem>> TryGetRemoteItemsAsync(Uri pkgVersionUri)
private async ValueTask<Dictionary<string, VersionItem>> TryGetRemoteItemsAsync(Uri pkgVersionUri)
{
try
{
@@ -187,7 +187,7 @@ internal sealed partial class PackageConverter
}
}
private async Task<bool> ReplaceGameResourceAsync(IEnumerable<ItemOperationInfo> operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress<PackageReplaceStatus> progress)
private async ValueTask<bool> ReplaceGameResourceAsync(IEnumerable<ItemOperationInfo> operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress<PackageReplaceStatus> progress)
{
// 重命名 _Data 目录
try
@@ -236,7 +236,7 @@ internal sealed partial class PackageConverter
return true;
}
private async Task ReplaceFromCacheOrWebAsync(string cacheFilePath, string targetFilePath, string scatteredFilesUrl, ItemOperationInfo info, IProgress<PackageReplaceStatus> progress)
private async ValueTask ReplaceFromCacheOrWebAsync(string cacheFilePath, string targetFilePath, string scatteredFilesUrl, ItemOperationInfo info, IProgress<PackageReplaceStatus> progress)
{
if (File.Exists(cacheFilePath))
{
@@ -291,7 +291,7 @@ internal sealed partial class PackageConverter
}
}
private async Task ReplacePackageVersionsAsync(string scatteredFilesUrl, string gameFolder)
private async ValueTask ReplacePackageVersionsAsync(string scatteredFilesUrl, string gameFolder)
{
foreach (string versionFilePath in Directory.EnumerateFiles(gameFolder, "*pkg_version"))
{
@@ -314,7 +314,7 @@ internal sealed partial class PackageConverter
}
}
private async Task<Dictionary<string, VersionItem>> GetRemoteVersionItemsAsync(Stream stream)
private async ValueTask<Dictionary<string, VersionItem>> GetRemoteVersionItemsAsync(Stream stream)
{
Dictionary<string, VersionItem> results = new();
using (StreamReader reader = new(stream))
@@ -334,7 +334,7 @@ internal sealed partial class PackageConverter
return results;
}
private async Task<Dictionary<string, VersionItem>> GetLocalVersionItemsAsync(Stream stream, ConvertDirection direction)
private async ValueTask<Dictionary<string, VersionItem>> GetLocalVersionItemsAsync(Stream stream, ConvertDirection direction)
{
Dictionary<string, VersionItem> results = new();

View File

@@ -109,8 +109,6 @@ internal static class ApiOsEndpoints
/// Game Authkey
/// </summary>
public const string BindingGenAuthKey = $"{ApiAccountOsBindingApi}/genAuthKey";
#endregion
#region BbsApiOsApi

View File

@@ -32,9 +32,11 @@ internal sealed partial class UserClient : IUserClient
public async Task<Response<UserFullInfoWrapper>> GetUserFullInfoAsync(Model.Entity.User user, CancellationToken token = default)
{
Response<UserFullInfoWrapper>? resp = await httpClient
//.SetUser(user, CookieType.SToken)
// .SetUser(user, CookieType.SToken)
.SetReferer(ApiEndpoints.BbsReferer)
//.UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
// .UseDynamicSecret(DynamicSecretVersion.Gen1, SaltType.K2, true)
.TryCatchGetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfoQuery(user.Aid!), options, logger, token)
.ConfigureAwait(false);

View File

@@ -38,11 +38,14 @@ internal readonly struct QueryString
}
}
#if NET8_0
private static QueryString Parse(ReadOnlySpan<char> value)
{
// TODO: .NET 8 ReadOnlySpan Split
return default;
}
#endif
/// <summary>
/// Parses a query string into a <see cref="QueryString"/> object. Keys/values are automatically URL decoded.