In FxCop's hey day, many rules were written to help validate the correctness and performance of .NET code. .NET has evolved significantly since then, however, and the creation of new rules has not kept up with the times; we've added very few new analyzers to help validate the correctness and performance of code using the wealth of new types, methods, and patterns that have made their way into .NET in the interim.
For .NET 5, we should augment https://github.com/dotnet/roslyn-analyzers/tree/master/src/Microsoft.NetCore.Analyzers with new analyzers to help further validate the correctness and improve the performance of code written for .NET.
Guidelines:
This issue exists to collect and catalog ideas for such rules to be implemented in the .NET 5 timeframe.
Note that the notes below are just starting ideas... cases called out may not be the right ones to check, may be missing additional cases of importance, etc. When we decide to tackle one of these, part of the work item (which should be split off into a separate issue) will be determining the right heuristics to employ (and potentially even deciding that the false positive rate will be too high to include such a rule at all).
code-analyzer Marks an issue that suggests a Roslyn analyzer
Edit: this work is actively happening and you can track progress here: https://github.com/dotnet/runtime/projects/46
Old List ### Correctness SystemHashCode usage. Find GetHashCode implementations doing manual hashcode combination and switch to HashCode.Combine.
ArgumentException arguments. Find places where nameof(…)
is passed as the first (rather than second) argument to ArgumentException
. Also places where nameof
is used as the argument name in an ArgumentException
(or derived type) but where it's not referring to a parameter.
Local DateTime math. Find places where DateTimes known to be in local time (e.g. resulting from DateTime.Now) are used in math.
Buffer.BlockCopy. The count argument is in bytes, but it's easy to accidentally do something like Buffer.BlockCopy(src, 0, dst, 0, src.Length)
, and if src is an array of elements larger than bytes, this is a bug.
Structs passed to Object.ReferenceEquals. Calls to ReferenceEquals
where we can detect a value type is being passed in are invariably wrong, as the value type will be boxed, and regardless of its value, ReferenceEquals
will always return false
.
ArrayPool misuse. The rule should flag cases where a buffer is potentially being returned to the shared pool multiple times, e.g. where a buffer is returned and then still used after the Return; where a method taking an array by ref Returns the array but doesn't null out the ref; where there's a Return call in a try block, where that same instance is also returned in a catch block, and where something after the Return call in the try block could cause an exception that would trigger the same instance to be returned again in the catch; etc.
MemoryManager finalizers. Adding a finalizer to a MemoryManager<T>
-derived type is likely an indication of a bug, as it suggests a native resource that could have been handed out in a Span<T>
is getting cleaned up and potentially while it’s still in use by the Span<T>
.
foreach (var item in src.Select(…).Where(…).AsParallel(…))
, is a nop and should either be removed or the AsParallel moved earlier in the query. I’ve even seen developers write foreach (var item in src.AsParallel())
thinking it parallelizes the foreach
loop, which it doesn’t… it’d be good to warn about such misuse.OfType
would provably result in an empty sequence, because we can see that the input type in the sequence will never be the specified type.[Immediate]
/[ConstantExpected
attribute that could be put on parameters to indicate that arguments should be constants rather than variables. See Static analysis for .NET 5 #30740 (comment) for details.SpinLock
is a mutable struct, meant only for advanced scenarios. Accidentally making a SpinLock
field readonly
can result in silent but significant problems, as any mutations to the instance (e.g. Enter, Exit) will be done on a compiler-generated copy and thus be ignored, making the lock an expensive nop. (It might make sense to extend this analyzer to additional mutable struct types where storing them in a readonly field is likely a bug, e.g. GCHandle
.)CancellationToken
should have been passed but wasn’t, e.g. in an async method that takes a CancellationToken
, a method is called that has an overload that takes a CancellationToken
but a shorter overload that doesn’t take a CancellationToken
was used instead. cc: @marklionew TaskCompletionSource<T>(object state)
. TCS has a ctor that takes a TaskCreationOptions options
and another ctor that takes an object state
. There’s a similar enum to TaskCreationOptions
that’s only meant to be used with ContinueWith
: TaskContinuationsOptions
. It’s easy to accidentally pass a TaskContinuationOptions
to the TCS ctor, in which case it binds to the overload accepting an object
and isn’t treating as options at all (and, adding insult to “your options aren’t respected” injury, it also boxes).ValueTask
/ValueTask<T>
correctness. The rule should detect cases where there's a strong liklihood a ValueTask{<T>}
is being used incorrectly, e.g. where a single instance may be awaited multiple times, where an instance may be awaited and then also returned out of a method, where an instance may have .GetAwaiter().GetResult()
called on it when it's not obviously already completed, when one is stored into a static field or a dictionary or some other publishing mechanism, etc.string.Concat with substrings. The rule should flag instances of a pattern like str1 + str2.Substring(…) + str3
or string.Concat(str1, str2.Substring(…), str3)
and instead switch to using the span-based string.Concat(str1, str2.AsSpan(…), str3)
.
AsSpan instead of Substring. Somewhat more generally, any time string.Substring
is used as an argument to something where there's an equivalent overload that takes a ReadOnlySpan<char>
(e.g. StringBuilder.Append(string)
vs StringBuilder.Append(ReadOnlySpan<char>)
), the case can be flagged to be changed to use AsSpan
instead.
Span.SequenceEquals. Identify open-coded comparison loops between two spans/arrays and suggest replacing with SequenceEquals.
static ReadOnlySpan<byte>
properties. With a pattern like private static readonly byte[] s_array = new byte[] { ... }
where the static readonly byte[]
is internal
/private
, all consumers of s_array
could instead operate on a span, and the field is initialized to a new byte[]
of constant values, it can be changed instead to static ReadOnlySpan<byte> Data => new byte[] { ... }
, and the C# compiler will optimize the implementation.
Replace local allocations with span stackallocs.. Flag places where known small temporary arrays of primitives (e.g. with a small constant length / where the total size of sizeof(T)*length can be determined to be < some threshold) not inside any loop and not passed around could be replaced by span stackallocs.
Calling stackalloc in loop. Calling stackalloc in a loop leads to stackoverflow. This bug is easy to introduce with the above.
string.Concat consolidation. Various patterns of string concatenation generate unnecessary intermediate strings, e.g. string result = s1 + s2; return result + s3;
will create an unnecessary string allocation, as will string result = s1 + s2; if (condition) result+= s3; return result;
which could be rewritten as string result = condition ? s1 + s2 + s3 : s1 + s2;
. The rule would find and offer fixes for such patterns.
Primitive substring parsing. The rule should flag instances of a pattern like int.Parse(str.Substring(…))
and instead switch to using the span-based int.Parse(str.AsSpan(…))
. This would apply to all of the primitive types, and more generally potentially anything that has an overload taking a ReadOnlySpan<char>
instead of a string
.
String.IndexOf(...) == -1. Calls to String.IndexOf(...)
where the result is then just compared to -1 can instead be replaced by calls to String.Contains(...)
.
StringBuilder.Append(char vs string). It's common to see calls to StringBuilder.Append(string)
with a const string
containing a single character, e.g. ","
. These would be slightly cheaper as calls using a const char
instead.
StringBuilder.Append(primitive.ToString()). The primitive should be passed directly instead.
Stream.ReadByte/WriteByte missing overrides. The rule should flag custom Stream-derived types that don’t override ReadByte
or WriteByte
.
Stream.ReadAsync/WriteAsync missing overrides. The rule should flag custom Stream-derived types that override BeginRead/EndRead
or BeginWrite/EndWrite
but that don’t override ReadAsync
or WriteAsync
. And it should flag custom Stream-derived types that override the array-based ReadAsync
or WriteAsync
but that don’t override the Memory
-based overloads of the same name. (Potentially the same should be done for the Span
-based overloads, but as the array-based Read
and Write
methods are abstract and thus must be overridden, it’s harder to say whether those should be or not.)
Stream.Read/WriteAsync overload usage. Find places where await stream.Read/WriteAsync(array, offset, length, …)
are used and recommend they be replaced by calls to the overloads that take {ReadOnly}Memory<byte>
, to benefit from the return type being ValueTask<int>
.
Tuple<…>
is being used but where a ValueTuple<…>
would suffice, ideally with the C# language syntax employed. There are some cases where a Tuple<…>
is beneficial, however, so the patterns identified here would be constrained.Nullable<T>.HasValue
, it's common to see calls to Nullable<T>.Value
; instead of calling Value
, it's less work to call GetValueOrDefault()
, as Value
repeats the HasValue
check. It's possible a future JIT could optimize away the duplicate check, but if nothing else using GetValueOrDefault()
makes the job of the JIT easier.params array allocation in loops. Find calls to System.* methods inside loops, where those methods take params arrays and those params arrays are being allocated, and hoist the allocation to before the loop if possible.
Lifting arrays of consts to statics. Arrays of consts passed to known methods on types like System.String (e.g. string.IndexOfAny(new[] { ',', '.' })
) can be lifted out to static readonly fields.
Redundant virtual property calls. Virtual properties are rarely inlined by the JIT and result into expensive virtual calls. Detect cases where the call can be hoisted and the return value reused.
constructor parameters should match property names. In general, constructor arguments who initialize should match properties (casing aside). There are also cases where serializers will match properties to constructor parameters in order to construct immutable types. This would address that too.
Dictionary.ContainsKey(key) followed by Dictionary.Remove(key). The pair can be combined into just the Remove
call.
Dictionary.ContainsKey(key) followed by Dictionary.this[key]. The pair can be combined into just a TryGetValue
call.
!Dictionary.ContainsKey(key) followed by Dictionary.Add. The pair can be combined into just a TryAdd
call.
Dictionary.ContainsKey(key), followed by Dictionary.this[key], followed by Dictionary.Remove(key). The trio can be combined into just the Remove
call, using the overload accepting out TValue value
.
sizeof
is much more efficient than Marshal.SizeOf
. In cases where they'll produce the same answer, the former should be preferred (it'll generally require using unsafe
, though, so this should only fire if unsafe
is allowed in the project).Using ValueTask<T>
instead of Task<T>
. Flagging internal/private methods that returns T
s that won’t be entirely cached (e.g. bool) and where every caller of the method only ever awaits its result directly.
Task.Delay in Task.WhenAny. Flag places where a Task.Delay is used as an argument to WhenAny and where that Task.Delay doesn’t take a cancellation token, in which case the Task.Delay is likely leaving a timer running for longer than is necessary.
Task.WhenAll with one argument. There’s no reason to call WhenAll with a single Task
; just use the Task
.
Task.WaitAll with one argument. Task.Wait
can be used instead.
if (token.IsCancellationRequested) throw new OperationCanceledException();
or if (token.IsCancellationRequested) throw new OperationCanceledException(token);
and replace with token.ThrowIfCancellationRequested();
.cc: @jaredpar, @mavasani, @danmosemsft
svick, martincostello, jakubmisek, ryanerdmann, hakenr and 76 morejnm2, Therzok, Gnbrkm41, holthe-tveit, jfbueno and 7 moreomariom, jnm2, mavasani, Gnbrkm41, Therzok and 18 moreveikkoeeva, paulomorgado, danmoseley, xsoheilalizadeh and Seteh
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4