Skip to content

Commit ca6b4dd

Browse files
Add docs for all the analyzers (#307)
* Add docs for all the analyzers * Update links in ReadMe.md to point to the correct documentation for analyzers
1 parent 973608c commit ca6b4dd

30 files changed

+3182
-418
lines changed

ReadMe.md

Lines changed: 53 additions & 293 deletions
Large diffs are not rendered by default.

docs/Rules/EPC11.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# EPC11 - Suspicious equality implementation
2+
3+
This analyzer detects suspicious implementations of `Equals` methods that may not be working as intended.
4+
5+
## Description
6+
7+
The analyzer warns when an `Equals` method implementation appears suspicious, typically when the method doesn't use any instance members or compares against other instances properly.
8+
9+
## Code that triggers the analyzer
10+
11+
```csharp
12+
public class MyClass
13+
{
14+
public override bool Equals(object obj)
15+
{
16+
// Suspicious: only using static members or not using 'this' instance
17+
return SomeStaticProperty == 42;
18+
}
19+
}
20+
```
21+
22+
```csharp
23+
public class Person
24+
{
25+
public string Name { get; set; }
26+
27+
public override bool Equals(object obj)
28+
{
29+
// Suspicious: parameter 'obj' is never used
30+
return this.Name == "test";
31+
}
32+
}
33+
```
34+
35+
## How to fix
36+
37+
Implement `Equals` method properly by:
38+
39+
1. Using the parameter that's passed in
40+
2. Checking instance members against the other object's members
41+
3. Following the standard equality pattern
42+
43+
```csharp
44+
public class Person
45+
{
46+
public string Name { get; set; }
47+
48+
public override bool Equals(object obj)
49+
{
50+
if (obj is Person other)
51+
{
52+
return this.Name == other.Name;
53+
}
54+
return false;
55+
}
56+
}
57+
```

docs/Rules/EPC12.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# EPC12 - Suspicious exception handling: only the 'Message' property is observed in the catch block
2+
3+
This analyzer detects catch blocks where only the `Message` property of an exception is used, which often indicates incomplete exception handling.
4+
5+
## Description
6+
7+
The analyzer warns when a catch block only accesses the `Message` property of an exception without observing the exception object itself or its other important properties like `InnerException`. This is suspicious because the `Message` property often contains generic information, while the actual useful data is in the exception object or its inner exceptions.
8+
9+
## Code that triggers the analyzer
10+
11+
```csharp
12+
try
13+
{
14+
// Some risky operation
15+
SomeMethod();
16+
}
17+
catch (Exception ex)
18+
{
19+
// Only using ex.Message - this triggers the warning
20+
Console.WriteLine(ex.Message);
21+
}
22+
```
23+
24+
25+
## How to fix
26+
27+
Use the full exception object or access additional properties like `InnerException`:
28+
29+
```csharp
30+
try
31+
{
32+
SomeMethod();
33+
}
34+
catch (Exception ex)
35+
{
36+
// Log the full exception object
37+
Console.WriteLine(ex.ToString());
38+
// Or access the exception object directly
39+
LogException(ex);
40+
}
41+
```

docs/Rules/EPC13.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# EPC13 - Suspiciously unobserved result
2+
3+
This analyzer detects when method return values that should typically be observed are being ignored.
4+
5+
## Description
6+
7+
The analyzer warns when the result of a method call is not being used, particularly for methods that return important values that should be observed. This helps catch cases where developers might have forgotten to use the return value of a method.
8+
9+
## Code that triggers the analyzer
10+
11+
```csharp
12+
public class Example
13+
{
14+
public void ProcessData()
15+
{
16+
// Return value is ignored - this triggers the warning
17+
GetImportantValue();
18+
19+
// String methods return new strings but result is ignored
20+
"hello".ToUpper();
21+
22+
// Collection methods that return new collections
23+
list.Where(x => x > 0);
24+
}
25+
26+
public string GetImportantValue()
27+
{
28+
return "important data";
29+
}
30+
}
31+
```
32+
33+
## How to fix
34+
35+
Observe (use) the return values:
36+
37+
```csharp
38+
public class Example
39+
{
40+
public void ProcessData()
41+
{
42+
// Store the result in a variable
43+
var importantValue = GetImportantValue();
44+
45+
// Use the result directly
46+
var upperText = "hello".ToUpper();
47+
Console.WriteLine(upperText);
48+
49+
// Chain operations or store result
50+
var filteredList = list.Where(x => x > 0).ToList();
51+
}
52+
53+
public string GetImportantValue()
54+
{
55+
return "important data";
56+
}
57+
}
58+
```
59+
60+
If you intentionally want to ignore the result, you can use discard:
61+
62+
```csharp
63+
_ = GetImportantValue(); // Explicitly ignore the result
64+
```

docs/Rules/EPC14.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# EPC14 - ConfigureAwait(false) call is redundant
2+
3+
This analyzer detects redundant calls to `ConfigureAwait(false)` when the assembly is configured not to require them.
4+
5+
## Description
6+
7+
The analyzer provides information (not a warning) when `ConfigureAwait(false)` calls are redundant because the assembly has been configured to not capture the synchronization context automatically. This helps clean up unnecessary code.
8+
9+
## Code that triggers the analyzer
10+
11+
```csharp
12+
public async Task ProcessAsync()
13+
{
14+
// ConfigureAwait(false) is redundant when assembly is configured for it
15+
await SomeAsyncMethod().ConfigureAwait(false);
16+
17+
// This is also redundant
18+
var result = await GetDataAsync().ConfigureAwait(false);
19+
}
20+
```
21+
22+
## How to fix
23+
24+
Remove the redundant `ConfigureAwait(false)` calls:
25+
26+
```csharp
27+
public async Task ProcessAsync()
28+
{
29+
// ConfigureAwait(false) behavior is already configured at assembly level
30+
await SomeAsyncMethod();
31+
32+
var result = await GetDataAsync();
33+
}
34+
```
35+
36+
Note: This rule only applies when your assembly is configured to not capture synchronization context by default. The configuration is typically done through project settings or attributes.

docs/Rules/EPC15.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# EPC15 - ConfigureAwait(false) must be used
2+
3+
This analyzer detects missing `ConfigureAwait(false)` calls when the assembly is configured to require them.
4+
5+
## Description
6+
7+
The analyzer warns when `ConfigureAwait(false)` should be used but is missing. This typically applies to library code where you want to avoid deadlocks by not capturing the synchronization context.
8+
9+
## Code that triggers the analyzer
10+
11+
```csharp
12+
public async Task ProcessAsync()
13+
{
14+
// Missing ConfigureAwait(false) - this triggers the warning
15+
await SomeAsyncMethod();
16+
17+
// Also missing ConfigureAwait(false)
18+
var result = await GetDataAsync();
19+
}
20+
```
21+
22+
## How to fix
23+
24+
Add `ConfigureAwait(false)` to all await expressions:
25+
26+
```csharp
27+
public async Task ProcessAsync()
28+
{
29+
// Add ConfigureAwait(false) to avoid capturing sync context
30+
await SomeAsyncMethod().ConfigureAwait(false);
31+
32+
var result = await GetDataAsync().ConfigureAwait(false);
33+
}
34+
```
35+
36+
## When to use ConfigureAwait(false)
37+
38+
Use `ConfigureAwait(false)` in:
39+
- Library code that doesn't need to return to the original synchronization context
40+
- Background processing code
41+
- Code that doesn't interact with UI elements
42+
43+
Don't use `ConfigureAwait(false)` in:
44+
- UI event handlers
45+
- Controller actions that need to return to the original context
46+
- Code that accesses UI elements after the await

docs/Rules/EPC16.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# EPC16 - Awaiting a result of a null-conditional expression will cause NullReferenceException
2+
3+
This analyzer detects dangerous patterns where awaiting the result of a null-conditional operator can cause a `NullReferenceException`.
4+
5+
## Description
6+
7+
The analyzer warns when code awaits the result of a null-conditional expression. When the left-hand side of the null-conditional operator is null, the expression returns null, and awaiting null will throw a `NullReferenceException`.
8+
9+
## Code that triggers the analyzer
10+
11+
```csharp
12+
public async Task ProcessAsync()
13+
{
14+
SomeService service = GetService(); // might return null
15+
16+
// Dangerous: if service is null, this returns null, and awaiting null throws NRE
17+
await service?.ProcessAsync();
18+
}
19+
```
20+
21+
```csharp
22+
public async Task<string> GetDataAsync()
23+
{
24+
var client = GetHttpClient(); // might return null
25+
26+
// This will throw NRE if client is null
27+
return await client?.GetStringAsync("http://example.com");
28+
}
29+
```
30+
31+
## How to fix
32+
33+
Check for null before awaiting, or use proper null handling:
34+
35+
```csharp
36+
public async Task ProcessAsync()
37+
{
38+
SomeService service = GetService();
39+
40+
// Option 1: Check for null first
41+
if (service != null)
42+
{
43+
await service.ProcessAsync();
44+
}
45+
46+
// Option 2: Use null-coalescing with Task.CompletedTask
47+
await (service?.ProcessAsync() ?? Task.CompletedTask);
48+
}
49+
```
50+
51+
```csharp
52+
public async Task<string> GetDataAsync()
53+
{
54+
var client = GetHttpClient();
55+
56+
// Check for null before using
57+
if (client == null)
58+
{
59+
return null; // or throw, or return default value
60+
}
61+
62+
return await client.GetStringAsync("http://example.com");
63+
}
64+
```

docs/Rules/EPC17.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# EPC17 - Avoid async-void delegates
2+
3+
This analyzer detects the use of async void delegates, which can cause application crashes due to unhandled exceptions.
4+
5+
## Description
6+
7+
The analyzer warns against using async void delegates. Unlike async void methods (which should only be used for event handlers), async void delegates are particularly dangerous because exceptions thrown in them cannot be caught and will crash the application.
8+
9+
## Code that triggers the analyzer
10+
11+
```csharp
12+
public void SetupHandlers()
13+
{
14+
// Dangerous: async void delegate
15+
SomeEvent += async () =>
16+
{
17+
await SomeAsyncMethod();
18+
// If this throws, it will crash the app
19+
};
20+
21+
// Another dangerous pattern
22+
Task.Run(async () =>
23+
{
24+
await ProcessAsync();
25+
// Exceptions here are unhandled
26+
});
27+
}
28+
```
29+
30+
```csharp
31+
public void RegisterCallback()
32+
{
33+
// Async void delegate in callback
34+
RegisterCallback(async () =>
35+
{
36+
await DoWorkAsync();
37+
});
38+
}
39+
```
40+
41+
## How to fix
42+
43+
Use regular void-return method and make a blocking call to async method.
44+
45+
```csharp
46+
public void SetupHandlers()
47+
{
48+
// Safe: async Task delegate
49+
SomeEvent += (sender, e) =>
50+
{
51+
try
52+
{
53+
SomeAsyncMethod().GetAwaiter().GetResult();
54+
}
55+
catch (Exception ex)
56+
{
57+
// Handle exception properly
58+
LogError(ex);
59+
}
60+
};}
61+
```

0 commit comments

Comments
 (0)