The .NET runtime ships with a GC (Garbage Collector) that is responsible for allocation and freeing up of memory as deemed necessary during an application's lifetime. It is responsible for automatically handling memory management-related tasks and preventing accidents such as memory leaks, accidental double frees, etc. Manually invoking the GC does not necessarily improve your application's performance and, in some cases, may even adversely impact the performance. If you wish to improve your application's performance, consider measures such as:
Finalizers, i.e., destructors, perform clean-up operations as an instance is picked up for garbage collection (GC). If a class has a finalizer defined, it is added to the Finalize
queue, which is later processed by the GC when deemed appropriate. An empty finalizer adds unnecessary additional overhead to the GC since it does not perform effective clean-up operations. Therefore, it is suggested that you either remove the empty finalizer or add relevant clean-up operations.
Environment.ProcessId
instead of Process.GetCurrentProcess().Id
CS-P1002Using Process.GetCurrentProcess().Id
requires that an instance of Process
be allocated to access the required Id
. It then adds additional overhead, i.e., disposing of the Process
instance. It is therefore suggested that you use the reliable and performant alternative Environment.ProcessId
.
static readonly
fields const
CS-P1003Expressions marked as static readonly
do not require an object/instance of a class and can be accessed directly.
Since they're marked as static, they cannot be modified outside a static
constructor. If such fields exist in a class without a static
constructor, it is recommended that you mark them as const
since expressions marked as const
are fully evaluated at the compile-time.
ToCharArray()
when iterating a string CS-P1004The ToCharArray()
returns a char
array whose elements are the individual characters of the string
on which this method is called. However, this call is particularly redundant within a foreach
statement as foreach
allows you to iterate through the types that implement IEnumerable
or IEnumerable<T>
, such as string
in this case. Therefore, it is recommended that you get rid of this redundant call.
Methods such as string.Contains
and string.IndexOf
allow you to search for an occurrence of either a single char or a substring within a string
. If you'd like to search for an occurrence of a single char, it is recommended that you use the appropriate method that takes a char
as a parameter rather than a string
as the former approach is more performant and recommended.
.Substring
method CS-P1007Index lookup methods such as IndexOf
, LastIndexOf
, IndexOfAny
, and LastIndexOfAny
return the index of the char
depending on the arguments supplied. Chaining such calls to .Substring
requires that a part of the string be selected and then the char be looked up. Instead, consider calling the index lookup methods directly and then subtracting the begin offset.
.TryGetValue
to access elements in Dictionary
CS-P1005The .ContainsKey
method allows you to check if a key exists in a Dictionary
while the indexer, i.e. []
allows you to directly access the specified key's value. Using both of them means that you'd be effectively accessing the Dictionary
twice. As an alternative, you can use .TryGetValue
to get a value from a Dictionary
that returns a bool
depending on whether the key exists or not.
.OrderBy()
calls as .ThenBy()
CS-P1008The .OrderBy()
method allows you to specify the criteria according to which the elements must be ordered and returns a new ordering. Everytime the .OrderBy()
method is invoked, the previous ordering, if any, is lost, resulting in a wastage of resources. To preserve any previous ordering, rewrite subsequent .OrderBy()
methods as .ThenBy()
.
Array.Empty<T>()
to efficiently create empty arrays CS-P1009Using the new
keyword lets you create an array with the length of your choice. However, memory allocation, no matter how big or small, comes at a cost. Instead, it is recommended that you use Array.Empty<T>()
to create an empty array as it maintains a readonly
static
buffer internally, thereby essentially maintaining a single instance instead of multiple copies despite multiple invocations. Refer to the benchmarks mentioned in the references section below for additional info.
virtual void Finalize()
as ~Destructor()
CS-P1010Destructors, often represented as ~Foo()
(where Foo = class name) are used to perform clean-up operations when being garbage collected. Such calls are translated to override void Finalize()
automatically and contain further instructions to invoke the Finalize
method recursively for all the instances in the inheritance chain to perform a full clean-up. It is therefore recommended that you let the compiler and runtime do the translation and that you not explicitly define virtual void Finalize()
. Additionally, if you wish to perform clean-up operations such as closing file handles and database connections, it is recommended that you take a look at the IDisposable
interface as Finalize
rs are invoked by the runtime when deemed appropriate.
Span<T>
instead of T[]
when possible to reduce allocations CS-P1011Span<T>
is a structure that is allocated on the stack rather than heap, thereby reducing the number of allocations during the program's lifetime which in return exerts less pressure on the GC. Since the method that you're trying to invoke has an overload that accepts Span<T>
, it is therefore suggested that you use that overload and pass Span<T>
rather than T[]
.
StringBuilder
CS-P1020Instantiating a new instance of StringBuilder
requires the initialization of the underneath buffer that holds the string
contents. Creating a new instance in a loop may have an adverse performance impact. Instead, it is recommended that you initialize StringBuilder
outside the loop and reuse it within the loop by clearing it when required.
ContainsKey()
to check if a key exists in a Dictionary<T, K>
CS-P1016If you wish to check if a key exists in a Dictionary
, consider using the ContainsKey()
that ideally has a complexity of O(1). Using the .Keys.Contains()
deteriorates the performance as it has a complexity of O(n) where n = number of elements in your Dictionary
.
Environment.ProcessId
to fetch process ID instead of Process.GetCurrentProcess().Id
CS-P1012You can use GetCurrentProcess().Id
to access the running program's process ID. However, this is an expensive call as it first allocates a Process
instance which then needs to be disposed, all just to get the running program's process ID. An efficient alternative is to just use the static
field Environment.ProcessId
.
Environment.ProcessPath
to fetch process path instead of Process.GetCurrentProcess().MainModule.FileName
CS-P1013You can use Process.GetCurrentProcess().MainModule.FileName
to access the running program's path. However, this is an expensive call as it first allocates a Process
instance which then needs to be disposed, all just to get the running program's path. An efficient alternative is to just use the static
field Environment.ProcessPath
.
Environment.CurrentManagedThreadId
to fetch the thread ID instead of Thread.CurrentThread.ManagedThreadId
CS-P1014You can use Thread.CurrentThread.ManagedThreadId
to access the running program's path. However, an easier alternative is to just use the static
field Environment.CurrentManagedThreadId
that does just the same. It is simpler and easier to read and remember.
Select()
is redundant CS-P1015The Select()
method from System.Linq
transforms an IEnumerable<T>
's elements based on the lambda specified. An identity function is a function that returns the same element unchanged that was passed as its argument. Since such a function does nothing useful, passing it to Select()
does not modify the elements and is just a redundant expression. It is recommended that you drop such redundant expressions.
StringBuilder.Append(char)
CS-P1017The Append()
method in StringBuilder
has overloads that accept both string
and char
. If you wish to append a single char, consider calling Append(char)
over Append(string)
as the former is more performant.
typeof(T).Assembly
to get currently executing assembly CS-P1018As the name suggests, Assembly.GetExecutingAssembly()
returns the executing assembly. However, this is not an efficient way. Consider using typeof(T).Assembly
instead.