collection experssion

This commit is contained in:
Lightczx
2023-11-15 16:19:43 +08:00
parent 163d076ed5
commit 0eade5e81a
56 changed files with 196 additions and 520 deletions

View File

@@ -109,6 +109,7 @@ dotnet_diagnostic.SA1629.severity = none
dotnet_diagnostic.SA1642.severity = none
dotnet_diagnostic.IDE0060.severity = none
dotnet_diagnostic.IDE0290.severity = none
# SA1208: System using directives should be placed before other using directives
dotnet_diagnostic.SA1208.severity = none
@@ -322,6 +323,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion
csharp_style_prefer_primary_constructors = true:suggestion
dotnet_diagnostic.SA1010.severity = none
[*.vb]
#### 命名样式 ####

View File

@@ -86,7 +86,7 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
private static void FillUpWithAddHttpClient(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
{
List<string> lines = new();
List<string> lines = [];
StringBuilder lineBuilder = new();
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))

View File

@@ -81,7 +81,7 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
private static void FillUpWithAddServices(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
{
List<string> lines = new();
List<string> lines = [];
StringBuilder lineBuilder = new();
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))

View File

@@ -41,10 +41,10 @@ public static class JsonParser
public static T? FromJson<T>(this string json)
{
// Initialize, if needed, the ThreadStatic variables
propertyInfoCache ??= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
fieldInfoCache ??= new Dictionary<Type, Dictionary<string, FieldInfo>>();
stringBuilder ??= new StringBuilder();
splitArrayPool ??= new Stack<List<string>>();
propertyInfoCache ??= [];
fieldInfoCache ??= [];
stringBuilder ??= new();
splitArrayPool ??= [];
// Remove all whitespace not within strings to make parsing simpler
stringBuilder.Length = 0;
@@ -99,7 +99,7 @@ public static class JsonParser
// Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
private static List<string> Split(string json)
{
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : new List<string>();
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : [];
splitArray.Clear();
if (json.Length == 2)
{

View File

@@ -20,7 +20,7 @@ internal static class EnumerableExtension
if (enumerator.MoveNext())
{
HashSet<TKey> set = new();
HashSet<TKey> set = [];
do
{

View File

@@ -39,10 +39,8 @@ public sealed class ResxGenerator : IIncrementalGenerator
private static void Execute(SourceProductionContext context, AnalyzerConfigOptionsProvider options, string? assemblyName, bool supportNullableReferenceTypes, ImmutableArray<AdditionalText> files)
{
// Group additional file by resource kind ((a.resx, a.en.resx, a.en-us.resx), (b.resx, b.en-us.resx))
List<IGrouping<string, AdditionalText>> resxGroups = files
.GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase)
.OrderBy(x => x.Key, StringComparer.Ordinal)
.ToList();
List<IGrouping<string, AdditionalText>> resxGroups = [.. files.GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase).OrderBy(x => x.Key, StringComparer.Ordinal)];
foreach (IGrouping<string, AdditionalText>? resxGroug in resxGroups)
{
@@ -400,7 +398,7 @@ public sealed class ResxGenerator : IIncrementalGenerator
private static List<ResxEntry>? LoadResourceFiles(SourceProductionContext context, IGrouping<string, AdditionalText> resxGroug)
{
List<ResxEntry> entries = new();
List<ResxEntry> entries = [];
foreach (AdditionalText? entry in resxGroug.OrderBy(file => file.Path, StringComparer.Ordinal))
{
SourceText? content = entry.GetText(context.CancellationToken);

View File

@@ -57,23 +57,13 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
private static void CompilationStart(CompilationStartAnalysisContext context)
{
SyntaxKind[] types =
{
SyntaxKind.ClassDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.EnumDeclaration,
};
SyntaxKind[] types = [SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.EnumDeclaration,];
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
SyntaxKind[] expressions =
{
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
SyntaxKind[] expressions = [SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression,];
context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions);
context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression);
context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression);

View File

@@ -7,6 +7,11 @@ namespace Snap.Hutao.Test;
[TestClass]
public class JsonSerializeTest
{
private readonly JsonSerializerOptions AlowStringNumberOptions = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
private const string SmapleObjectJson = """
{
"A" :1
@@ -44,12 +49,7 @@ public class JsonSerializeTest
[TestMethod]
public void NumberStringKeyCanSerializeAsKey()
{
JsonSerializerOptions options = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, AlowStringNumberOptions)!;
Assert.AreEqual(sample[111], "12");
}

View File

@@ -9,10 +9,7 @@ public sealed class ForEachRuntimeBehaviorTest
[TestMethod]
public void ListOfStringCanEnumerateAsReadOnlySpanOfChar()
{
List<string> strings = new()
{
"a", "b", "c"
};
List<string> strings = ["a", "b", "c"];
int count = 0;
foreach (ReadOnlySpan<char> chars in strings)

View File

@@ -8,8 +8,8 @@ public sealed class RangeRuntimeBehaviorTest
[TestMethod]
public void RangeTrimLastOne()
{
int[] array = { 1, 2, 3, 4 };
int[] test = { 1, 2, 3 };
int[] array = [1, 2, 3, 4];
int[] test = [1, 2, 3];
int[] result = array[..^1];
Assert.AreEqual(3, result.Length);
Assert.IsTrue(MemoryExtensions.SequenceEqual<int>(test, result));

View File

@@ -6,7 +6,7 @@ public sealed class UnsafeRuntimeBehaviorTest
[TestMethod]
public unsafe void UInt32AllSetIs()
{
byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, };
byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF,];
fixed (byte* pBytes = bytes)
{

View File

@@ -27,9 +27,9 @@ internal sealed class DefaultItemCollectionTransitionProvider : ItemCollectionTr
protected override void StartTransitions(IList<ItemCollectionTransition> transitions)
{
List<ItemCollectionTransition> addTransitions = new();
List<ItemCollectionTransition> removeTransitions = new();
List<ItemCollectionTransition> moveTransitions = new();
List<ItemCollectionTransition> addTransitions = [];
List<ItemCollectionTransition> removeTransitions = [];
List<ItemCollectionTransition> moveTransitions = [];
foreach (ItemCollectionTransition transition in addTransitions)
{

View File

@@ -118,7 +118,7 @@ internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
Span<double> columnHeights = new double[numberOfColumns];
Span<int> itemsPerColumn = new int[numberOfColumns];
HashSet<int> deadColumns = new();
HashSet<int> deadColumns = [];
for (int i = 0; i < context.ItemCount; i++)
{

View File

@@ -9,9 +9,9 @@ namespace Snap.Hutao.Control.Layout;
internal sealed class UniformStaggeredLayoutState
{
private readonly List<UniformStaggeredItem> items = new();
private readonly List<UniformStaggeredItem> items = [];
private readonly VirtualizingLayoutContext context;
private readonly Dictionary<int, UniformStaggeredColumnLayout> columnLayout = new();
private readonly Dictionary<int, UniformStaggeredColumnLayout> columnLayout = [];
private double lastAverageHeight;
public UniformStaggeredLayoutState(VirtualizingLayoutContext context)
@@ -32,7 +32,7 @@ internal sealed class UniformStaggeredLayoutState
{
if (!this.columnLayout.TryGetValue(columnIndex, out UniformStaggeredColumnLayout? columnLayout))
{
columnLayout = new();
columnLayout = [];
this.columnLayout[columnIndex] = columnLayout;
}

View File

@@ -11,7 +11,6 @@ internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
{
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));
[SuppressMessage("", "IDE0052")]
private FrameworkElement? presenter;
public Loading()

View File

@@ -62,7 +62,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
/// <inheritdoc/>
public void Remove(Uri uriForCachedItem)
{
Remove(new ReadOnlySpan<Uri>(uriForCachedItem));
Remove(new ReadOnlySpan<Uri>(ref uriForCachedItem));
}
/// <inheritdoc/>
@@ -76,7 +76,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
string folder = GetCacheFolder();
string[] files = Directory.GetFiles(folder);
List<string> filesToDelete = new();
List<string> filesToDelete = [];
foreach (ref readonly Uri uri in uriForCachedItems)
{
string filePath = Path.Combine(folder, GetCacheFileName(uri));

View File

@@ -12,7 +12,8 @@ namespace Snap.Hutao.Core;
internal sealed class CommandLineBuilder
{
private const char WhiteSpace = ' ';
private readonly Dictionary<string, string?> options = new();
private readonly Dictionary<string, string?> options = [];
/// <summary>
/// 当符合条件时添加参数

View File

@@ -18,7 +18,7 @@ internal static class IniSerializer
/// <returns>Ini 元素集合</returns>
public static List<IniElement> Deserialize(FileStream fileStream)
{
List<IniElement> results = new();
List<IniElement> results = [];
using (StreamReader reader = new(fileStream))
{
while (reader.ReadLine() is { } line)

View File

@@ -34,7 +34,7 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
private static IEnumerable<int> EnumerateNumbers(string source)
{
// TODO: Use Collection Literals
foreach (StringSegment id in new StringTokenizer(source, new[] { Comma }))
foreach (StringSegment id in new StringTokenizer(source, [Comma]))
{
yield return int.Parse(id.AsSpan(), CultureInfo.CurrentCulture);
}

View File

@@ -37,7 +37,7 @@ internal static class AppInstanceExtension
SetEvent(redirectEventHandle);
});
ReadOnlySpan<HANDLE> handles = new(redirectEventHandle);
ReadOnlySpan<HANDLE> handles = new(ref redirectEventHandle);
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
// TODO: Release handle

View File

@@ -45,9 +45,7 @@ internal static class DispatcherQueueExtension
});
blockEvent.Wait();
#pragma warning disable CA1508
exceptionDispatchInfo?.Throw();
#pragma warning restore CA1508
}
}
}

View File

@@ -8,6 +8,7 @@ using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Core.Windowing.HotKey;
[SuppressMessage("", "CA1001")]
[Injection(InjectAs.Singleton, typeof(IHotKeyController))]
[ConstructorGenerated]
internal sealed partial class HotKeyController : IHotKeyController
{
@@ -53,10 +54,10 @@ internal sealed partial class HotKeyController : IHotKeyController
while (!token.IsCancellationRequested)
{
INPUT[] inputs =
{
[
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN),
CreateInputForMouseEvent(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP),
};
];
if (SendInput(inputs.AsSpan(), sizeof(INPUT)) is 0)
{

View File

@@ -204,6 +204,6 @@ internal sealed class WindowController
// 48 is the navigation button leftInset
RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale);
appTitleBar.SetDragRectangles(dragRect.ToArray());
appTitleBar.SetDragRectangles([dragRect]);
}
}

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Windowing;
internal static class WindowExtension
{
private static readonly ConditionalWeakTable<Window, WindowController> WindowControllers = new();
private static readonly ConditionalWeakTable<Window, WindowController> WindowControllers = [];
public static void InitializeController<TWindow>(this TWindow window, IServiceProvider serviceProvider)
where TWindow : Window, IWindowOptionsSource

View File

@@ -33,7 +33,8 @@ internal sealed class WindowSubclass : IDisposable
this.window = window;
this.options = options;
this.serviceProvider = serviceProvider;
hotKeyController = new HotKeyController(serviceProvider);
hotKeyController = serviceProvider.GetRequiredService<IHotKeyController>();
}
/// <summary>

View File

@@ -80,7 +80,7 @@ internal static partial class EnumerableExtension
public static Dictionary<TKey, TSource> ToDictionaryIgnoringDuplicateKeys<TKey, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
where TKey : notnull
{
Dictionary<TKey, TSource> dictionary = new();
Dictionary<TKey, TSource> dictionary = [];
foreach (TSource value in source)
{
@@ -94,7 +94,7 @@ internal static partial class EnumerableExtension
public static Dictionary<TKey, TValue> ToDictionaryIgnoringDuplicateKeys<TKey, TValue, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> elementSelector)
where TKey : notnull
{
Dictionary<TKey, TValue> dictionary = new();
Dictionary<TKey, TValue> dictionary = [];
foreach (TSource value in source)
{

View File

@@ -69,7 +69,7 @@ internal static partial class EnumerableExtension
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<TSource> EmptyIfNull<TSource>(this List<TSource>? source)
{
return source ?? new();
return source ?? [];
}
public static List<T> GetRange<T>(this List<T> list, in Range range)

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Specialized;
namespace Snap.Hutao.Extension;
internal static partial class EnumerableExtension
{
public static bool TryGetValue(this NameValueCollection collection, string name, [NotNullWhen(true)] out string? value)
{
if (collection.AllKeys.Contains(name))
{
if (collection.GetValues(name) is [string single])
{
value = single;
return true;
}
}
value = string.Empty;
return false;
}
}

View File

@@ -30,20 +30,6 @@ internal static partial class EnumerableExtension
return source ?? Enumerable.Empty<TSource>();
}
/// <summary>
/// 将源转换为仅包含单个元素的枚举
/// </summary>
/// <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;
}
/// <summary>
/// 寻找枚举中唯一的值,找不到时
/// 回退到首个或默认值

View File

@@ -1,29 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Extension;
/// <summary>
/// 对象拓展
/// </summary>
internal static class ObjectExtension
{
/// <summary>
/// 转换到只有1长度的数组
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="source">源</param>
/// <returns>数组</returns>
#if NET8_0
[Obsolete("Use C# 12 Collection Literals")]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] ToArray<T>(this T source)
{
// TODO: use C# 12 collection literals
// [ source ]
return new[] { source };
}
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Frozen;
namespace Snap.Hutao.Model.InterChange.Achievement;
/// <summary>
@@ -15,11 +17,7 @@ internal sealed class UIAF
/// </summary>
public const string CurrentVersion = "v1.1";
// TODO use FrozenSet
private static readonly HashSet<string> SupportedVersion = new()
{
CurrentVersion,
};
private static readonly FrozenSet<string> SupportedVersion = FrozenSet.ToFrozenSet([CurrentVersion]);
/// <summary>
/// 信息

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Frozen;
using System.Collections.Immutable;
namespace Snap.Hutao.Model.InterChange.Inventory;
@@ -16,10 +17,7 @@ internal sealed class UIIF
/// </summary>
public const string CurrentVersion = "v1.0";
private static readonly ImmutableList<string> SupportedVersion = new List<string>()
{
CurrentVersion,
}.ToImmutableList();
private static readonly FrozenSet<string> SupportedVersion = FrozenSet.ToFrozenSet([CurrentVersion]);
/// <summary>
/// 信息

View File

@@ -58,8 +58,8 @@ internal sealed class MonsterBaseValue : BaseValue
{
get
{
return new()
{
return
[
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_FIRE_SUB_HURT, FireSubHurt),
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_WATER_SUB_HURT, WaterSubHurt),
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_GRASS_SUB_HURT, GrassSubHurt),
@@ -68,7 +68,7 @@ internal sealed class MonsterBaseValue : BaseValue
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_ICE_SUB_HURT, IceSubHurt),
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_ROCK_SUB_HURT, RockSubHurt),
FightPropertyFormat.ToNameValue(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, PhysicalSubHurt),
};
];
}
}
}

View File

@@ -129,10 +129,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.Achievements
.AsNoTracking()
.Where(i => i.ArchiveId == archiveId)
.ToList();
return [.. appDbContext.Achievements.AsNoTracking().Where(i => i.ArchiveId == archiveId)];
}
}
@@ -154,7 +151,7 @@ internal sealed partial class AchievementDbService : IAchievementDbService
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.AchievementArchives.AsNoTracking().ToList();
return [.. appDbContext.AchievementArchives.AsNoTracking()];
}
}

View File

@@ -21,7 +21,7 @@ internal sealed partial class AchievementStatisticsService : IAchievementStatist
{
await taskContext.SwitchToBackgroundAsync();
List<AchievementStatistics> results = new();
List<AchievementStatistics> results = [];
foreach (AchievementArchive archive in await achievementDbService.GetAchievementArchiveListAsync().ConfigureAwait(false))
{
int finishedCount = await achievementDbService

View File

@@ -21,14 +21,14 @@ internal sealed partial class AppOptions : DbStoreOptions
{
private readonly List<NameValue<BackdropType>> supportedBackdropTypesInner = CollectionsNameValue.ListFromEnum<BackdropType>();
private readonly List<NameValue<string>> supportedCulturesInner = new()
{
private readonly List<NameValue<string>> supportedCulturesInner =
[
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
ToNameValue(CultureInfo.GetCultureInfo("en")),
ToNameValue(CultureInfo.GetCultureInfo("ko")),
ToNameValue(CultureInfo.GetCultureInfo("ja")),
};
];
private string? gamePath;
private string? powerShellPath;
@@ -126,7 +126,7 @@ internal sealed partial class AppOptions : DbStoreOptions
string? paths = Environment.GetEnvironmentVariable("Path");
if (!string.IsNullOrEmpty(paths))
{
foreach (StringSegment path in new StringTokenizer(paths, ';'.ToArray()))
foreach (StringSegment path in new StringTokenizer(paths, [';']))
{
if (path is { HasValue: true, Length: > 0 })
{

View File

@@ -243,7 +243,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
if (distinctCount < dbInfos.Count)
{
avatarInfoDbService.RemoveAvatarInfoRangeByUid(uid);
dbInfos = new();
dbInfos = [];
}
}
}

View File

@@ -19,7 +19,7 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid).ToList();
return [.. appDbContext.AvatarInfos.AsNoTracking().Where(i => i.Uid == uid)];
}
}

View File

@@ -38,7 +38,7 @@ internal sealed partial class CultivationService : ICultivationService
Guid projectId = cultivateProject.InnerId;
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = new();
List<InventoryItemView> results = [];
foreach (Material meta in metadata.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value))
{
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
@@ -64,7 +64,7 @@ internal sealed partial class CultivationService : ICultivationService
List<CultivateEntryView> resultEntries = new(entries.Count);
foreach (CultivateEntry entry in entries)
{
List<CultivateItemView> entryItems = new();
List<CultivateItemView> entryItems = [];
foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
{
entryItems.Add(new(item, materials.Single(m => m.Id == item.ItemId)));
@@ -94,7 +94,7 @@ internal sealed partial class CultivationService : ICultivationService
CancellationToken token)
{
await taskContext.SwitchToBackgroundAsync();
List<StatisticsCultivateItem> resultItems = new();
List<StatisticsCultivateItem> resultItems = [];
Guid projectId = cultivateProject.InnerId;

View File

@@ -66,11 +66,12 @@ internal struct GachaLogFetchContext
/// </summary>
/// <param name="configType">卡池类型</param>
/// <param name="query">查询</param>
[SuppressMessage("", "SA1010")]
public void ResetForProcessingType(GachaConfigType configType, in GachaLogQuery query)
{
DbEndId = null;
CurrentType = configType;
ItemsToAdd = new();
ItemsToAdd = [];
FetchStatus = new(configType);
QueryOptions = new(query, configType);
}

View File

@@ -4,7 +4,8 @@
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Request.QueryString;
using System.Collections.Specialized;
using System.Web;
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
@@ -27,10 +28,11 @@ internal sealed partial class GachaLogQueryManualInputProvider : IGachaLogQueryP
if (isOk)
{
QueryString query = QueryString.Parse(queryString);
NameValueCollection query = HttpUtility.ParseQueryString(queryString);
if (query.TryGetValue("auth_appid", out string? appId) && appId is "webview_gacha")
{
string queryLanguageCode = query["lang"];
string? queryLanguageCode = query["lang"];
if (metadataOptions.IsCurrentLocale(queryLanguageCode))
{
return new(true, new(queryString));

View File

@@ -4,9 +4,10 @@
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.User;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Request;
using Snap.Hutao.Web.Response;
using System.Collections.Specialized;
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
@@ -37,7 +38,7 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid
if (authkeyResponse.IsOk())
{
return new(true, new(GachaLogQueryOptions.ToQueryString(data, authkeyResponse.Data, metadataOptions.LanguageCode)));
return new(true, new(ComposeQueryString(data, authkeyResponse.Data, metadataOptions.LanguageCode)));
}
else
{
@@ -49,4 +50,17 @@ internal sealed partial class GachaLogQuerySTokenProvider : IGachaLogQueryProvid
return new(false, SH.MustSelectUserAndUid);
}
}
[SuppressMessage("", "SA1010")]
private static string ComposeQueryString(GenAuthKeyData genAuthKeyData, GameAuthKey gameAuthKey, string lang)
{
NameValueCollection collection = [];
collection.Set("lang", lang);
collection.Set("auth_appid", genAuthKeyData.AuthAppId);
collection.Set("authkey", gameAuthKey.AuthKey);
collection.Set("authkey_ver", $"{gameAuthKey.AuthKeyVersion:D}");
collection.Set("sign_type", $"{gameAuthKey.SignType:D}");
return collection.ToQueryString();
}
}

View File

@@ -4,10 +4,11 @@
using Snap.Hutao.Core.IO;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Web.Request.QueryString;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace Snap.Hutao.Service.GachaLog.QueryProvider;
@@ -84,8 +85,8 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv
return new(false, SH.ServiceGachaLogUrlProviderCacheUrlNotFound);
}
QueryString query = QueryString.Parse(result.TrimEnd("#/log"));
string queryLanguageCode = query["lang"];
NameValueCollection query = HttpUtility.ParseQueryString(result.TrimEnd("#/log"));
string? queryLanguageCode = query["lang"];
if (metadataOptions.IsCurrentLocale(queryLanguageCode))
{

View File

@@ -108,7 +108,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
/// </summary>
/// <param name="languageCode">语言代码</param>
/// <returns>是否为当前语言名称</returns>
public bool IsCurrentLocale(string languageCode)
public bool IsCurrentLocale(string? languageCode)
{
if (string.IsNullOrEmpty(languageCode))
{

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<RootNamespace>Snap.Hutao</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
@@ -278,6 +278,7 @@
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.6.11" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231008000" />
@@ -304,6 +305,7 @@
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="Extension\EnumerableExtension.NameValueCollection.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -252,6 +252,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
}
}
[SuppressMessage("", "SA1010")]
[Command("ExportAsUIAFToFileCommand")]
private async Task ExportAsUIAFToFileAsync()
{
@@ -260,7 +261,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
string fileName = $"{achievementService.CurrentArchive?.Name}.json";
Dictionary<string, IList<string>> fileTypes = new()
{
[SH.ViewModelAchievementExportFileType] = ".json".Enumerate().ToList(),
[SH.ViewModelAchievementExportFileType] = [".json"],
};
FileSavePicker picker = pickerFactory

View File

@@ -6,7 +6,6 @@ using Snap.Hutao.View.Control;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Bridge;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Request.QueryString;
namespace Snap.Hutao.ViewModel.DailyNote;
@@ -19,7 +18,7 @@ internal sealed class DailyNoteWebViewerSource : IWebViewerSource
public string GetSource(UserAndUid userAndUid)
{
QueryString query = userAndUid.Uid.ToQueryString();
string query = userAndUid.Uid.ToQueryString();
return $"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/?{query}";
}
}

View File

@@ -231,6 +231,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
}
}
[SuppressMessage("", "SA1010")]
[Command("ExportToUIGFJsonCommand")]
private async Task ExportToUIGFJsonAsync()
{
@@ -238,7 +239,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
{
Dictionary<string, IList<string>> fileTypes = new()
{
[SH.ViewModelGachaLogExportFileType] = ".json".Enumerate().ToList(),
[SH.ViewModelGachaLogExportFileType] = [".json"],
};
FileSavePicker picker = pickerFactory.GetFileSavePicker(

View File

@@ -22,11 +22,12 @@ internal static class WeaponFilter
return (object o) => o is Weapon weapon && DoFilter(input, weapon);
}
[SuppressMessage("", "SA1010")]
private static bool DoFilter(string input, Weapon weapon)
{
List<bool> matches = new();
List<bool> matches = [];
foreach (StringSegment segment in new StringTokenizer(input, ' '.Enumerate().ToArray()))
foreach (StringSegment segment in new StringTokenizer(input, [' ']))
{
string value = segment.ToString();

View File

@@ -2,8 +2,8 @@
// Licensed under the MIT license.
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Request.QueryString;
using System.Collections.Specialized;
using System.Web;
namespace Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
@@ -45,7 +45,7 @@ internal struct GachaLogQueryOptions
/// size
/// end_id
/// </summary>
private readonly QueryString innerQuery;
private readonly NameValueCollection innerQuery;
/// <summary>
/// 构造一个新的祈愿记录请求配置
@@ -60,36 +60,17 @@ internal struct GachaLogQueryOptions
// 对于每个类型我们需要单独创建
// 对应类型的 GachaLogQueryOptions
Type = queryType;
innerQuery = QueryString.Parse(query.Query);
// innerQuery.Set("lang", "zh-cn");
innerQuery.Set("gacha_type", (int)queryType);
innerQuery.Set("size", Size);
}
/// <summary>
/// 转换到查询字符串
/// </summary>
/// <param name="genAuthKeyData">生成信息</param>
/// <param name="gameAuthKey">验证包装</param>
/// <param name="lang">语言</param>
/// <returns>查询</returns>
public static string ToQueryString(GenAuthKeyData genAuthKeyData, GameAuthKey gameAuthKey, string lang)
{
QueryString queryString = new();
queryString.Set("lang", lang);
queryString.Set("auth_appid", genAuthKeyData.AuthAppId);
queryString.Set("authkey", Uri.EscapeDataString(gameAuthKey.AuthKey));
queryString.Set("authkey_ver", gameAuthKey.AuthKeyVersion);
queryString.Set("sign_type", gameAuthKey.SignType);
return queryString.ToString();
innerQuery = HttpUtility.ParseQueryString(query.Query);
innerQuery.Set("gacha_type", $"{queryType:D}");
innerQuery.Set("size", $"{Size}");
}
public readonly string ToQueryString()
{
// Make the cached end id into query.
innerQuery.Set("end_id", EndId);
return innerQuery.ToString();
innerQuery.Set("end_id", $"{EndId:D}");
string? query = innerQuery.ToString();
ArgumentException.ThrowIfNullOrEmpty(query);
return query;
}
}

View File

@@ -1,18 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Request.QueryString;
using Snap.Hutao.Web.Request;
using System.Collections.Specialized;
namespace Snap.Hutao.Web.Hoyolab;
internal static class PlayerUidExtension
{
public static QueryString ToQueryString(this in PlayerUid playerUid)
[SuppressMessage("", "SA1010")]
public static string ToQueryString(this in PlayerUid playerUid)
{
QueryString queryString = new();
queryString.Set("role_id", playerUid.Value);
queryString.Set("server", playerUid.Region);
NameValueCollection collection = [];
collection.Set("role_id", playerUid.Value);
collection.Set("server", playerUid.Region);
return queryString;
return collection.ToQueryString();
}
}

View File

@@ -14,12 +14,13 @@ internal sealed class ReliquarySetsConverter : JsonConverter<ReliquarySets>
private const char Separator = ',';
/// <inheritdoc/>
[SuppressMessage("", "SA1010")]
public override ReliquarySets? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.GetString() is { } source)
{
List<ReliquarySet> sets = new();
foreach (StringSegment segment in new StringTokenizer(source, Separator.ToArray()))
List<ReliquarySet> sets = [];
foreach (StringSegment segment in new StringTokenizer(source, [Separator]))
{
if (segment is { HasValue: true, Length: > 0 })
{

View File

@@ -0,0 +1,41 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Specialized;
using System.Text;
using System.Web;
namespace Snap.Hutao.Web.Request;
internal static class NameValueCollectionExtension
{
public static string ToQueryString(this NameValueCollection collection)
{
int count = collection.Count;
if (count == 0)
{
return string.Empty;
}
StringBuilder sb = new();
string?[] keys = collection.AllKeys;
for (int i = 0; i < count; i++)
{
string? key = keys[i];
if (collection.GetValues(key) is { } values)
{
foreach (string value in values)
{
if (!string.IsNullOrEmpty(key))
{
sb.Append(key).Append('=');
}
sb.Append(HttpUtility.UrlEncode(value)).Append('&');
}
}
}
return sb.Length > 0 ? sb.ToString(0, sb.Length - 1) : string.Empty;
}
}

View File

@@ -1,296 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Request.QueryString;
/// <summary>
/// querystring serializer/deserializer
/// </summary>
[HighQuality]
internal readonly struct QueryString
{
private readonly Dictionary<string, List<string>> dictionary = new();
/// <summary>
/// Nothing
/// </summary>
public QueryString()
{
}
/// <summary>
/// <para>Gets the first value of the first parameter with the matching name. </para>
/// <para>Throws <see cref="KeyNotFoundException"/> if a parameter with a matching name could not be found. </para>
/// <para>O(n) where n = Count of the current object.</para>
/// </summary>
/// <param name="name">The parameter name to find.</param>
/// <returns>query</returns>
public string this[string name]
{
get
{
if (TryGetValue(name, out string? value))
{
return value;
}
throw new KeyNotFoundException($"A parameter with name '{name}' could not be found.");
}
}
#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.
/// </summary>
/// <param name="queryString">
/// The query string to deserialize.
/// Valid input would be something like "a=1&amp;b=5".
/// URL decoding of keys/values is automatically performed.
/// Also supports query strings that are serialized using ; instead of &amp;, like "a=1;b=5"</param>
/// <returns>A new QueryString represents the url query</returns>
public static QueryString Parse(string? queryString)
{
if (string.IsNullOrWhiteSpace(queryString))
{
return new QueryString();
}
int questionMarkIndex = queryString.AsSpan().IndexOf('?');
queryString = queryString[(questionMarkIndex + 1)..];
string[] pairs = queryString.Split('&', ';');
QueryString answer = new();
foreach (string pair in pairs)
{
string name;
string? value;
int indexOfEquals = pair.IndexOf('=', StringComparison.Ordinal);
if (indexOfEquals == -1)
{
name = pair;
value = string.Empty;
}
else
{
name = pair[..indexOfEquals];
value = pair[(indexOfEquals + 1)..];
}
answer.Add(name, value);
}
return answer;
}
/// <summary>
/// Gets the first value of the first parameter with the matching name. If no parameter with a matching name exists, returns false.
/// </summary>
/// <param name="name">The parameter name to find.</param>
/// <param name="value">The parameter's value will be written here once found.</param>
/// <returns>value</returns>
public bool TryGetValue(string name, [NotNullWhen(true)] out string? value)
{
if (dictionary.TryGetValue(name, out List<string>? values))
{
value = values.First();
return true;
}
value = null;
return false;
}
/// <summary>
/// Gets the values of the parameter with the matching name. If no parameter with a matching name exists, sets <paramref name="values"/> to null and returns false.
/// </summary>
/// <param name="name">The parameter name to find.</param>
/// <param name="values">The parameter's values will be written here once found.</param>
/// <returns>values</returns>
public bool TryGetValues(string name, [NotNullWhen(true)] out string?[]? values)
{
if (dictionary.TryGetValue(name, out List<string>? storedValues))
{
values = storedValues.ToArray();
return true;
}
values = null;
return false;
}
/// <summary>
/// Returns the count of parameters in the current query string.
/// </summary>
/// <returns>count of the queries</returns>
public int Count()
{
return dictionary.Select(i => i.Value.Count).Sum();
}
/// <summary>
/// Adds a query string parameter to the query string.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="value">The optional value of the parameter.</param>
public void Add(string name, string value)
{
if (!dictionary.TryGetValue(name, out List<string>? values))
{
values = new List<string>();
dictionary[name] = values;
}
values.Add(value);
}
/// <inheritdoc cref="Set(string, string)"/>
public void Set(string name, object value)
{
Set(name, value.ToString() ?? string.Empty);
}
/// <summary>
/// Sets a query string parameter. If there are existing parameters with the same name, they are removed.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="value">The optional value of the parameter.</param>
public void Set(string name, string value)
{
dictionary[name] = new() { value };
}
/// <summary>
/// Determines if the query string contains at least one parameter with the specified name.
/// </summary>
/// <param name="name">The parameter name to look for.</param>
/// <returns>True if the query string contains at least one parameter with the specified name, else false.</returns>
public bool Contains(string name)
{
return dictionary.ContainsKey(name);
}
/// <summary>
/// Determines if the query string contains a parameter with the specified name and value.
/// </summary>
/// <param name="name">The parameter name to look for.</param>
/// <param name="value">The value to look for when the name has been matched.</param>
/// <returns>True if the query string contains a parameter with the specified name and value, else false.</returns>
public bool Contains(string name, string value)
{
return dictionary.TryGetValue(name, out List<string>? values) && values.Contains(value);
}
/// <summary>
/// Removes the first parameter with the specified name.
/// </summary>
/// <param name="name">The name of parameter to remove.</param>
/// <returns>True if the parameters were removed, else false.</returns>
public bool Remove(string name)
{
if (dictionary.TryGetValue(name, out List<string>? values))
{
if (values.Count == 1)
{
dictionary.Remove(name);
}
else
{
values.RemoveAt(0);
}
return true;
}
return false;
}
/// <summary>
/// Removes all parameters with the specified name.
/// </summary>
/// <param name="name">The name of parameters to remove.</param>
/// <returns>True if the parameters were removed, else false.</returns>
public bool RemoveAll(string name)
{
return dictionary.Remove(name);
}
/// <summary>
/// Removes the first parameter with the specified name and value.
/// </summary>
/// <param name="name">The name of the parameter to remove.</param>
/// <param name="value">value</param>
/// <returns>True if parameter was removed, else false.</returns>
public bool Remove(string name, string value)
{
if (dictionary.TryGetValue(name, out List<string>? values))
{
if (values.RemoveFirstWhere(i => Equals(i, value)))
{
// If removed last value, remove the key
if (values.Count == 0)
{
dictionary.Remove(name);
}
return true;
}
}
return false;
}
/// <summary>
/// Removes all parameters with the specified name and value.
/// </summary>
/// <param name="name">The name of parameters to remove.</param>
/// <param name="value">The value to match when deciding whether to remove.</param>
/// <returns>The count of parameters removed.</returns>
public int RemoveAll(string name, string value)
{
if (dictionary.TryGetValue(name, out List<string>? values))
{
int countRemoved = values.RemoveAll(i => Equals(i, value));
// If removed last value, remove the key
if (values.Count == 0)
{
dictionary.Remove(name);
}
return countRemoved;
}
return 0;
}
/// <summary>
/// Serializes the key-value pairs into a query string, using the default &amp; separator.
/// Produces something like "a=1&amp;b=5".
/// URL encoding of keys/values is automatically performed.
/// Null values are not written (only their key is written).
/// </summary>
/// <returns>query</returns>
public override string ToString()
{
return string.Join('&', GetParameters());
}
private IEnumerable<QueryStringParameter> GetParameters()
{
foreach ((string name, List<string> values) in dictionary)
{
foreach (string value in values)
{
yield return new QueryStringParameter(name, value);
}
}
}
}

View File

@@ -1,38 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Request.QueryString;
/// <summary>
/// A single query string parameter (name and value pair).
/// </summary>
[HighQuality]
internal struct QueryStringParameter
{
/// <summary>
/// The name of the parameter. Cannot be null.
/// </summary>
public string Name;
/// <summary>
/// The value of the parameter (or null if there's no value).
/// </summary>
public string Value;
/// <summary>
/// Initializes a new query string parameter with the specified name and optional value.
/// </summary>
/// <param name="name">The name of the parameter. Cannot be null.</param>
/// <param name="value">The optional value of the parameter.</param>
internal QueryStringParameter(string name, string value)
{
Name = name;
Value = value;
}
/// <inheritdoc/>
public override readonly string ToString()
{
return $"{Name}={Value}";
}
}

1
src/Snap.Hutao/Temp.txt Normal file
View File

@@ -0,0 +1 @@