diff --git a/Common.ruleset b/Common.ruleset
index c743a52470..5b8d7aa745 100644
--- a/Common.ruleset
+++ b/Common.ruleset
@@ -28,6 +28,9 @@
+
+
+
diff --git a/ExternalProjects/BizHawk.Analyzer/FirstOrDefaultOnStructAnalyzer.cs b/ExternalProjects/BizHawk.Analyzer/FirstOrDefaultOnStructAnalyzer.cs
new file mode 100644
index 0000000000..e68f46f01e
--- /dev/null
+++ b/ExternalProjects/BizHawk.Analyzer/FirstOrDefaultOnStructAnalyzer.cs
@@ -0,0 +1,51 @@
+namespace BizHawk.Analyzers;
+
+using System.Collections.Immutable;
+using System.Linq;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class FirstOrDefaultOnStructAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly DiagnosticDescriptor DiagUseFirstOrNull = new(
+ id: "BHI3100",
+ title: "Call to FirstOrDefault when elements are of a value type; FirstOrNull may have been intended",
+ messageFormat: "Call to FirstOrDefault when elements are of a value type; did you mean FirstOrNull?",
+ category: "Usage",
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagUseFirstOrNull);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterCompilationStartAction(initContext =>
+ {
+ if (initContext.Compilation.GetTypeByMetadataName("BizHawk.Common.CollectionExtensions.CollectionExtensions") is null) return; // project does not have BizHawk.Common dependency
+ var linqExtClassSym = initContext.Compilation.GetTypeByMetadataName("System.Linq.Enumerable")!;
+ IMethodSymbol? firstOrDefaultNoPredSym = null;
+ IMethodSymbol? firstOrDefaultWithPredSym = null;
+ foreach (var sym in linqExtClassSym.GetMembers("FirstOrDefault").Cast())
+ {
+ if (sym.Parameters.Length is 2) firstOrDefaultWithPredSym = sym;
+ else firstOrDefaultNoPredSym = sym;
+ }
+ initContext.RegisterOperationAction(
+ oac =>
+ {
+ var operation = (IInvocationOperation) oac.Operation;
+ var calledSym = operation.TargetMethod.ConstructedFrom;
+ if (!(firstOrDefaultWithPredSym!.Matches(calledSym) || firstOrDefaultNoPredSym!.Matches(calledSym))) return;
+ var receiverExprType = (INamedTypeSymbol) operation.SemanticModel.GetTypeInfo((CSharpSyntaxNode) operation.Arguments[0].Syntax)!.ConvertedType!;
+ if (receiverExprType.TypeArguments[0].IsValueType) oac.ReportDiagnostic(Diagnostic.Create(DiagUseFirstOrNull, operation.Syntax.GetLocation()));
+ },
+ OperationKind.Invocation);
+ });
+ }
+}
diff --git a/References/BizHawk.Analyzer.dll b/References/BizHawk.Analyzer.dll
index 54b9ce45fa..aac0933a98 100644
Binary files a/References/BizHawk.Analyzer.dll and b/References/BizHawk.Analyzer.dll differ
diff --git a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs
index 44a57e5055..18bf6294f3 100644
--- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs
+++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs
@@ -6,6 +6,7 @@ using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Common;
+using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Client.EmuHawk
{
@@ -197,7 +198,7 @@ namespace BizHawk.Client.EmuHawk
{
if (e.IsPressed(Keys.Enter) || e.IsPressed(Keys.Tab))
{
- var k = HotkeyInfo.AllHotkeys.FirstOrDefault(kvp => string.Compare(kvp.Value.DisplayName, SearchBox.Text, true) is 0).Key;
+ var k = HotkeyInfo.AllHotkeys.FirstOrNull(kvp => string.Compare(kvp.Value.DisplayName, SearchBox.Text, true) is 0)?.Key;
// Found
if (k is not null)
diff --git a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs
index 1e79cbc52a..a768bd584b 100644
--- a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs
+++ b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs
@@ -16,6 +16,7 @@ using BizHawk.Emulation.Common;
using BizHawk.Client.Common;
using BizHawk.Client.EmuHawk.Properties;
using BizHawk.Client.EmuHawk.ToolExtensions;
+using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Client.EmuHawk
{
@@ -242,13 +243,13 @@ namespace BizHawk.Client.EmuHawk
{
if (_textTable.Any())
{
- var byteArr = new List();
- foreach (var chr in str)
+ var byteArr = new byte[str.Length];
+ for (var i = 0; i < str.Length; i++)
{
- byteArr.Add((byte)_textTable.FirstOrDefault(kvp => kvp.Value == chr).Key);
+ var c = str[i];
+ byteArr[i] = (byte) (_textTable.FirstOrNull(kvp => kvp.Value == c)?.Key ?? 0);
}
-
- return byteArr.ToArray();
+ return byteArr;
}
return str.Select(Convert.ToByte).ToArray();
diff --git a/src/BizHawk.Common/HawkFile/HawkFile.cs b/src/BizHawk.Common/HawkFile/HawkFile.cs
index 33ffdabe4d..3c15b49dfc 100644
--- a/src/BizHawk.Common/HawkFile/HawkFile.cs
+++ b/src/BizHawk.Common/HawkFile/HawkFile.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
+using BizHawk.Common.CollectionExtensions;
+
namespace BizHawk.Common
{
///
@@ -257,7 +259,8 @@ namespace BizHawk.Common
}
/// finds an ArchiveItem with the specified name (path) within the archive; returns null if it doesnt exist
- public HawkArchiveFileItem? FindArchiveMember(string? name) => ArchiveItems.FirstOrDefault(ai => ai.Name == name);
+ public HawkArchiveFileItem? FindArchiveMember(string? name)
+ => ArchiveItems.FirstOrNull(ai => ai.Name == name);
/// a stream for the currently bound file
/// no stream bound (haven't called or overload)
diff --git a/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs b/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs
index 3f4900eccf..ddace068a9 100644
--- a/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs
+++ b/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using BizHawk.Common;
+using BizHawk.Common.CollectionExtensions;
//this would be a good place for structural validation
//after this step, we won't want to have to do stuff like that (it will gunk up already sticky code)
@@ -109,10 +110,10 @@ namespace BizHawk.Emulation.DiscSystem.CUE
public override string ToString()
{
- var idx = Indexes.FirstOrDefault(cci => cci.Number == 1);
- if (idx.Number != 1) return $"T#{Number:D2} NO INDEX 1";
+ var idx = Indexes.FirstOrNull(static cci => cci.Number is 1);
+ if (idx is null) return $"T#{Number:D2} NO INDEX 1";
var indexlist = string.Join("|", Indexes);
- return $"T#{Number:D2} {BlobIndex}:{idx.FileMSF} ({indexlist})";
+ return $"T#{Number:D2} {BlobIndex}:{idx.Value.FileMSF} ({indexlist})";
}
}