StrictAnalyzers 1.3.0
dotnet add package StrictAnalyzers --version 1.3.0
NuGet\Install-Package StrictAnalyzers -Version 1.3.0
<PackageReference Include="StrictAnalyzers" Version="1.3.0" />
<PackageVersion Include="StrictAnalyzers" Version="1.3.0" />
<PackageReference Include="StrictAnalyzers" />
paket add StrictAnalyzers --version 1.3.0
#r "nuget: StrictAnalyzers, 1.3.0"
#:package StrictAnalyzers@1.3.0
#addin nuget:?package=StrictAnalyzers&version=1.3.0
#tool nuget:?package=StrictAnalyzers&version=1.3.0
StrictAnalyzers Documentation
Please put a star for this project if you find out it is useful! Thank you!
Get Started
All settings are required except commented as optional
<PropertyGroup>
<NoWarn>
$(NoWarn);
</NoWarn>
<WarningsAsErrors>
$(WarningsAsErrors);
CS8019;
IDE0005;
IDE0130;
RS1032;
CA1508;
STAR_NO_ASSIGN;
STAR_NO_NULLABLE_CHECKS_FOR_NON_NULL_TYPES;
STAR_NO_POST_DECREMENT;
STAR_NO_POST_INCREMENT;
STAR_NO_PRE_DECREMENT;
STAR_NO_PRE_INCREMENT;
STAR_REQUIRED_PROPERTIES;
STAR_REQUIRED_FIELDS;
STAR_MAX_LINE_LENGTH;
STAR_IMMUTABLE_CLASSES;
STAR_NO_MUTABLE_COLLECTIONS;
STAR_NO_MUTABLE_PARAMETERS;
</WarningsAsErrors>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<RunCodeAnalysis>true</RunCodeAnalysis>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>
Rules
- STAR_IMMUTABLE_CLASSES - Class Immutability Analyzer
- STAR_NO_ASSIGN - No Assignment Analyzer
- STAR_REQUIRED_FIELDS - Required Fields Analyzer
- STAR_NO_MUTABLE_PARAMETERS - Method Parameter Immutability Analyzer
- STAR_NO_MUTABLE_COLLECTIONS - No Mutable Collections Analyzer
- STAR_REQUIRED_PROPERTIES - Required Properties Analyzer
- STAR_IMMUTABLE_RECORDS - Record Immutability Analyzer
- STAR_MAX_LINE_LENGTH - Max Line Length Analyzer
- STAR_NO_PRE_INCREMENT - Pre-Increment Analyzer
- STAR_NO_POST_INCREMENT - Post-Increment Analyzer
- STAR_NO_PRE_DECREMENT - Pre-Decrement Analyzer
- STAR_NO_POST_DECREMENT - Post-Decrement Analyzer
- STAR_NO_NULLABLE_CHECKS_FOR_NON_NULL_TYPES - No Nullable Null Check Analyzer
- STAR_NO_THROW - No Throw Analyzer
Class Immutability Analyzer
Diagnostic ID: STAR_IMMUTABLE_CLASSES
Category: Design
Severity: Warning
Description: Classes should be immutable to ensure thread safety and prevent unexpected state changes. This analyzer detects classes with mutable properties or non-readonly fields.
Incorrect Code Examples
// ❌ Class with mutable properties
public class Person
{
public string Name { get; set; } // Mutable setter
public int Age { get; set; } // Mutable setter
private string _email; // Non-readonly field
public string Email
{
get => _email;
set => _email = value; // Mutable setter
}
}
// ❌ Class with non-readonly fields
public class Configuration
{
private string _connectionString; // Should be readonly
public string ConnectionString
{
get => _connectionString;
set => _connectionString = value;
}
}
Correct Code Examples
// ✅ Immutable class with init-only properties
public class Person
{
public string Name { get; init; } // Init-only setter
public int Age { get; init; } // Init-only setter
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
// ✅ Immutable class with readonly fields
public class Configuration
{
private readonly string _connectionString;
public string ConnectionString => _connectionString;
public Configuration(string connectionString)
{
_connectionString = connectionString;
}
}
// ✅ Class with expression-bodied properties (readonly)
public class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
No Assignment Analyzer
Diagnostic ID: STAR_NO_ASSIGN
Category: Syntax
Severity: Warning
Description: Assignment operations are not allowed outside of variable declaration, object initialization, or constructor contexts. This analyzer enforces strict control over when assignments can occur.
Incorrect Code Examples
// ❌ Assignment outside of declaration
public class Example
{
private string _name;
public void UpdateName(string newName)
{
_name = newName; // Assignment not allowed
}
public void ProcessData()
{
int value = 10;
value = 20; // Assignment not allowed
}
}
// ❌ Assignment in method body
public void DoSomething()
{
var config = new Configuration();
config.IsEnabled = true; // Assignment not allowed
}
Correct Code Examples
// ✅ Assignment in variable declaration
public class Example
{
private readonly string _name;
public Example(string name)
{
_name = name; // Allowed in constructor
}
public void ProcessData()
{
int value = 20; // Declaration with assignment
var result = ProcessValue(value);
}
}
// ✅ Assignment in object initialization
public void CreateConfiguration()
{
var config = new Configuration
{
IsEnabled = true, // Allowed in object initializer
Name = "Default"
};
}
// ✅ Assignment in constructor
public class Person
{
private readonly string _name;
private readonly int _age;
public Person(string name, int age)
{
_name = name; // Allowed in constructor
_age = age; // Allowed in constructor
}
}
Required Fields Analyzer
Diagnostic ID: STAR_REQUIRED_FIELDS
Category: Syntax
Severity: Warning
Description: Public fields must be declared as required to ensure they are properly initialized. This analyzer enforces the use of the required modifier for public fields.
Incorrect Code Examples
// ❌ Public field without required modifier
public class Person
{
public string Name; // Should be required
public int Age; // Should be required
}
// ❌ Public field without required modifier
public class Configuration
{
public string ConnectionString; // Should be required
public bool IsEnabled; // Should be required
}
Correct Code Examples
// ✅ Public fields with required modifier
public class Person
{
public required string Name;
public required int Age;
}
// ✅ Public fields with required modifier
public class Configuration
{
public required string ConnectionString;
public required bool IsEnabled;
}
// ✅ Non-public fields (no required needed)
public class Example
{
private string _name; // Private - no required needed
internal int _age; // Internal - no required needed
protected string _description; // Protected - no required needed
}
// ✅ Static or const fields (no required needed)
public class Constants
{
public static string DefaultName = "Unknown";
public const int MaxAge = 150;
}
Method Parameter Immutability Analyzer
Diagnostic ID: STAR_NO_MUTABLE_PARAMETERS
Category: Immutability
Severity: Warning
Description: Method parameters should be immutable to prevent unexpected side effects. This analyzer detects when mutable types are used as method parameters.
Configuration
The analyzer can be configured using an .editorconfig file or analyzer configuration options:
[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_types = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces_start_with = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces_start_with = System.Collections.Immutable,System.Threading
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_types = CustomType,System.Collections.Generic.IReadOnlyCollection`1,System.Collections.Generic.IReadOnlyDictionary`2,Task,CustomType
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces = MyNamespace,MyNamespace2
... where `1 and `2 mean number of type parameters for generic types.
FROM VERSION 1.1.0 COMMA , USED AS DELIMITER FOR ARRAY PARAMETERS, because ;
mean "comment line" in .editorconfig according to format documentation.
Configuration Parameters:
use_predefined_immutable_types: Boolean flag to enable/disable predefined immutable types (e.g.,string,int,DateTime). Default:trueuse_predefined_immutable_namespaces: Boolean flag to enable/disable predefined immutable namespaces (e.g.,System.Threading.Tasks,System.Collections.Immutable). Default:trueimmutable_types: Comma-separated list of custom types to treat as immutableimmutable_namespaces: Comma-separated list of custom namespaces to treat as immutableuse_predefined_immutable_namespaces_start_with: Boolean flag to enable/disable predefined immutable namespaces partsimmutable_namespaces_start_with: Comma-separated list of custom namespaces first part to treat as immutable
Incorrect Code Examples
// ❌ Mutable types as parameters
public class Example
{
public void ProcessPerson(Person person) // Person is mutable
{
person.Name = "Updated"; // Modifies the parameter
}
public void UpdateList(List<string> items) // List is mutable
{
items.Add("New Item"); // Modifies the parameter
}
public void ModifyArray(int[] numbers) // Array is mutable
{
numbers[0] = 999; // Modifies the parameter
}
}
// ❌ Mutable reference types
public void ProcessObject(object obj) // Object is mutable
{
// obj can be modified
}
Correct Code Examples
// ✅ Immutable types as parameters
public class Example
{
public void ProcessPerson(Person person) // Person is immutable
{
var updatedPerson = person with { Name = "Updated" }; // Create new instance
}
public void ProcessList(IReadOnlyList<string> items) // Read-only interface
{
foreach (var item in items)
{
// Process without modification
}
}
public void ProcessArray(ReadOnlySpan<int> numbers) // Read-only span
{
foreach (var number in numbers)
{
// Process without modification
}
}
}
// ✅ Value types (immutable by default)
public void ProcessValue(int number, string text, DateTime date)
{
// These parameters cannot be modified
}
// ✅ Immutable reference types
public void ProcessString(string text) // String is immutable
{
var upperText = text.ToUpper(); // Creates new string
}
// ✅ Using readonly parameters
public void ProcessData(in Person person) // in parameter (readonly)
{
// person cannot be modified
}
Configuration Examples
Example 1: Disabling Predefined Immutable Types
[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_types = false
With this configuration, even built-in types like string, int, and DateTime will be flagged as mutable:
// ❌ Will now produce diagnostics with predefined_immutable_types = false
public void ProcessData(string text, int number, DateTime date)
{
// These parameters will now be flagged as mutable
}
Example 2: Adding Custom Immutable Types
[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_types = CustomImmutableType,MyNamespace.AnotherType
With this configuration, custom types are treated as immutable even if they have mutable properties:
// ✅ No diagnostic - CustomImmutableType is configured as immutable
public class CustomImmutableType
{
public int Value { get; set; } // Mutable property but type is configured as immutable
}
public void ProcessData(CustomImmutableType custom)
{
// No diagnostic - type is configured as immutable
}
Example 3: Adding Custom Immutable Namespaces
[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces = MyNamespace,ThirdParty.Immutable
With this configuration, all types in specified namespaces are treated as immutable:
namespace MyNamespace
{
public class CustomType
{
public int Value { get; set; } // Mutable property but in immutable namespace
}
}
// ✅ No diagnostic - MyNamespace is configured as immutable
public void ProcessData(MyNamespace.CustomType custom)
{
// No diagnostic - namespace is configured as immutable
}
Example 4: Combined Configuration
[*.cs]
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_types = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.use_predefined_immutable_namespaces_start_with = false
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces_start_with = System.Collections.Immutable,System.Threading
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_types = CustomType,System.Collections.Generic.IReadOnlyCollection`1,System.Collections.Generic.IReadOnlyDictionary`2",Task,CustomType
dotnet_diagnostic.STAR_NO_MUTABLE_PARAMETERS.immutable_namespaces = MyNamespace,MyNamespace2
This configuration disables all predefined immutable types and namespaces, but adds custom ones:
using System.Threading.Tasks;
namespace MyNamespace
{
public class CustomType
{
public int Value { get; set; }
}
}
public class MyClass
{
public void MyMethod(
string text, // ❌ Diagnostic - predefined types disabled
Task task, // ❌ Diagnostic - predefined namespaces disabled
MyNamespace.CustomType c, // ✅ No diagnostic - custom namespace
CustomType type) // ✅ No diagnostic - custom type
{
}
}
No Mutable Collections Analyzer
Diagnostic ID: STAR_NO_MUTABLE_COLLECTIONS
Category: Immutability
Severity: Warning
Description: Collections should be immutable or readonly to ensure thread safety and prevent unexpected state changes. This analyzer detects when mutable collection types are used in variable declarations, method parameters, return types, and properties.
What the Analyzer Detects
The analyzer checks for mutable collection usage in:
- Variable declarations (e.g.,
var list = new List<int>()) - Method parameters (e.g.,
public void Process(List<int> items)) - Method return types (e.g.,
public List<int> GetItems()) - Properties (e.g.,
public List<int> Items { get; set; }) - Local functions and arrow functions (e.g.,
() => new List<string>()) - Conditional operators (e.g.,
var result = condition ? new List<int>() : new List<int>())
What the Analyzer Allows
The analyzer allows:
- Immutable collections from
System.Collections.Immutablenamespace - Readonly interfaces like
IReadOnlyList<T>,IReadOnlyCollection<T>,IReadOnlyDictionary<TKey, TValue> - Non-collection types like
string,int,DateTime, etc.
Configuration
You can extend the set of types treated as immutable for this analyzer via .editorconfig. Use the following option to whitelist fully qualified type names (including arity suffix like \1, `2` for generics):
dotnet_diagnostic.STAR_NO_MUTABLE_COLLECTIONS.consider_as_immutable = System.Collections.Generic.List`1
- Place this under the desired scope in your .editorconfig (for example, under
[*.cs]). - Multiple values can be provided as a comma-separated list.
- Generic types must include the arity (number of generic parameters) as shown above.
Incorrect Code Examples
// ❌ Variable declarations with mutable collections
public class Example
{
public void ProcessData()
{
List<int> numbers = new List<int>(); // Diagnostic
Dictionary<string, int> scores = new Dictionary<string, int>(); // Diagnostic
HashSet<string> uniqueItems = new HashSet<string>(); // Diagnostic
Queue<double> values = new Queue<double>(); // Diagnostic
Stack<bool> flags = new Stack<bool>(); // Diagnostic
LinkedList<string> items = new LinkedList<string>(); // Diagnostic
// LINQ operations that return mutable collections
var list = Enumerable.Range(1, 10).ToList(); // Diagnostic
var dict = items.ToDictionary(x => x, x => x.Length); // Diagnostic
}
}
// ❌ Method parameters with mutable collections
public class DataProcessor
{
public void ProcessItems(List<int> items) // Diagnostic
{
// Process items
}
public void ProcessScores(Dictionary<string, int> scores) // Diagnostic
{
// Process scores
}
}
// ❌ Method return types with mutable collections
public class DataProvider
{
public List<string> GetNames() // Diagnostic
{
return new List<string> { "John", "Jane" };
}
public Dictionary<int, string> GetLookup() // Diagnostic
{
return new Dictionary<int, string>();
}
}
// ❌ Properties with mutable collections
public class Configuration
{
public List<string> AllowedUsers { get; set; } // Diagnostic
public Dictionary<string, object> Settings { get; } // Diagnostic
}
// ❌ Constructor parameters with mutable collections
public class Person
{
public Person(List<string> names, Dictionary<string, int> scores) // Diagnostic
{
// Initialize
}
}
// ❌ Local functions with mutable collections
public class Example
{
public void ProcessData()
{
List<int> GetNumbers() // Diagnostic
{
return new List<int>();
}
var numbers = GetNumbers(); // Diagnostic
}
}
// ❌ Arrow functions with mutable collections
public class Example
{
public void ProcessData()
{
var getList = () => new List<int>(); // Diagnostic
var getDict = () => new Dictionary<string, int>(); // Diagnostic
var list = getList(); // Diagnostic
var dict = getDict(); // Diagnostic
}
}
// ❌ Conditional operators with mutable collections
public class Example
{
public void ProcessData(bool useFirst)
{
var collection = useFirst ? new List<int>() : new List<int>(); // Diagnostic
var dict = useFirst ? new Dictionary<string, int>() : new Dictionary<string, int>(); // Diagnostic
}
}
Correct Code Examples
// ✅ Using readonly interfaces
public class Example
{
public void ProcessData()
{
IReadOnlyList<int> numbers = ImmutableList<int>.Empty;
IReadOnlyDictionary<string, int> scores = ImmutableDictionary<string, int>.Empty;
IReadOnlyCollection<string> uniqueItems = ImmutableHashSet<string>.Empty;
}
}
// ✅ Method parameters with readonly interfaces
public class DataProcessor
{
public void ProcessItems(IReadOnlyList<int> items) // No diagnostic
{
// Process items without modification
foreach (var item in items)
{
// Process item
}
}
public void ProcessScores(IReadOnlyDictionary<string, int> scores) // No diagnostic
{
// Process scores without modification
foreach (var kvp in scores)
{
// Process key-value pair
}
}
}
// ✅ Method return types with readonly interfaces
public class DataProvider
{
public IReadOnlyList<string> GetNames() // No diagnostic
{
return ImmutableList<string>.Empty.Add("John").Add("Jane");
}
public IReadOnlyDictionary<int, string> GetLookup() // No diagnostic
{
return ImmutableDictionary<int, string>.Empty;
}
}
// ✅ Properties with readonly interfaces
public class Configuration
{
public IReadOnlyList<string> AllowedUsers { get; } // No diagnostic
public IReadOnlyDictionary<string, object> Settings { get; } // No diagnostic
}
// ✅ Constructor parameters with readonly interfaces
public class Person
{
public Person(IReadOnlyList<string> names, IReadOnlyDictionary<string, int> scores) // No diagnostic
{
// Initialize
}
}
// ✅ Using immutable collections from System.Collections.Immutable
public class Example
{
public void ProcessData()
{
var numbers = ImmutableList<int>.Empty.Add(1).Add(2).Add(3); // No diagnostic
var scores = ImmutableDictionary<string, int>.Empty.Add("John", 95); // No diagnostic
var uniqueItems = ImmutableHashSet<string>.Empty.Add("A").Add("B"); // No diagnostic
var queue = ImmutableQueue<double>.Empty.Enqueue(1.5).Enqueue(2.7); // No diagnostic
var stack = ImmutableStack<bool>.Empty.Push(true).Push(false); // No diagnostic
}
}
// ✅ Local functions with readonly interfaces
public class Example
{
public void ProcessData()
{
IReadOnlyList<int> GetNumbers() // No diagnostic
{
return ImmutableList<int>.Empty.Add(1).Add(2).Add(3);
}
var numbers = GetNumbers(); // No diagnostic
}
}
// ✅ Arrow functions with readonly interfaces
public class Example
{
public void ProcessData()
{
var getList = () => ImmutableList<int>.Empty; // No diagnostic
var getDict = () => ImmutableDictionary<string, int>.Empty; // No diagnostic
var list = getList(); // No diagnostic
var dict = getDict(); // No diagnostic
}
}
// ✅ Conditional operators with compatible types
public class Example
{
public void ProcessData(bool useFirst)
{
var collection = useFirst ? ImmutableList<int>.Empty : ImmutableList<int>.Empty; // No diagnostic
var dict = useFirst ? ImmutableDictionary<string, int>.Empty : ImmutableDictionary<string, int>.Empty; // No diagnostic
}
}
// ✅ Converting mutable collections to readonly
public class Example
{
public void ProcessData()
{
var mutableList = new List<int> { 1, 2, 3 };
var mutableDict = new Dictionary<string, int> { ["A"] = 1, ["B"] = 2 };
// Convert to readonly for public API
IReadOnlyList<int> readonlyList = mutableList.AsReadOnly(); // No diagnostic
IReadOnlyDictionary<string, int> readonlyDict = mutableDict.AsReadOnly(); // No diagnostic
}
}
// ✅ Non-collection types (no diagnostics)
public class Example
{
public void ProcessData()
{
string text = "Hello World"; // No diagnostic
int number = 42; // No diagnostic
DateTime date = DateTime.Now; // No diagnostic
var point = new { X = 10, Y = 20 }; // No diagnostic
}
}
Best Practices
- Use readonly interfaces (
IReadOnlyList<T>,IReadOnlyCollection<T>,IReadOnlyDictionary<TKey, TValue>) for public APIs - Use immutable collections from
System.Collections.Immutablenamespace when possible - Convert mutable collections to readonly using
.AsReadOnly()method before exposing them - Prefer immutable collections for thread-safe scenarios
- Use readonly properties instead of setters for collection properties
Migration Guide
To migrate from mutable collections to immutable ones:
// Before (mutable)
public class OldExample
{
public List<string> Items { get; set; }
public void AddItem(string item)
{
Items.Add(item); // Modifies the collection
}
}
// After (immutable)
public class NewExample
{
public IReadOnlyList<string> Items { get; private set; }
public void AddItem(string item)
{
var newItems = Items.ToList(); // Create new list
newItems.Add(item); // Modify the new list
Items = newItems.AsReadOnly(); // Assign readonly version
}
}
// Alternative using ImmutableList
public class BetterExample
{
public IReadOnlyList<string> Items { get; private set; } = ImmutableList<string>.Empty;
public void AddItem(string item)
{
Items = Items.Add(item); // ImmutableList.Add returns new instance
}
}
Required Properties Analyzer
Diagnostic ID: STAR_REQUIRED_PROPERTIES
Category: Syntax
Severity: Warning
Description: Public properties must be declared as required to ensure they are properly initialized. This analyzer enforces the use of the required modifier for public properties.
Incorrect Code Examples
// ❌ Public properties without required modifier
public class Person
{
public string Name { get; set; } // Should be required
public int Age { get; set; } // Should be required
public string Email { get; set; } // Should be required
}
// ❌ Public properties without required modifier
public class Configuration
{
public string ConnectionString { get; init; } // Should be required
public bool IsEnabled { get; init; } // Should be required
}
Correct Code Examples
// ✅ Public properties with required modifier
public class Person
{
public required string Name { get; set; }
public required int Age { get; set; }
public required string Email { get; set; }
}
// ✅ Public properties with required modifier
public class Configuration
{
public required string ConnectionString { get; init; }
public required bool IsEnabled { get; init; }
}
// ✅ Non-public properties (no required needed)
public class Example
{
private string Name { get; set; } // Private - no required needed
internal int Age { get; set; } // Internal - no required needed
protected string Email { get; set; } // Protected - no required needed
}
// ✅ Static properties (no required needed)
public class Constants
{
public static string DefaultName { get; set; } = "Unknown";
public static int MaxAge { get; } = 150;
}
Record Immutability Analyzer
Diagnostic ID: STAR_IMMUTABLE_RECORDS
Category: Design
Severity: Warning
Description: Records should be immutable to ensure thread safety and prevent unexpected state changes. This analyzer detects records with mutable properties or non-readonly fields.
Incorrect Code Examples
// ❌ Record with mutable properties
public record Person
{
public string Name { get; set; } // Mutable setter
public int Age { get; set; } // Mutable setter
}
// ❌ Record with mutable fields
public record Configuration
{
private string _connectionString; // Non-readonly field
public string ConnectionString
{
get => _connectionString;
set => _connectionString = value; // Mutable setter
}
}
Correct Code Examples
// ✅ Immutable record with init-only properties
public record Person
{
public string Name { get; init; } // Init-only setter
public int Age { get; init; } // Init-only setter
}
// ✅ Immutable record with readonly fields
public record Configuration
{
private readonly string _connectionString;
public string ConnectionString => _connectionString;
public Configuration(string connectionString)
{
_connectionString = connectionString;
}
}
// ✅ Positional record (immutable by default)
public record Point(int X, int Y);
// ✅ Record with expression-bodied properties (readonly)
public record Circle
{
public double Radius { get; }
public double Area => Math.PI * Radius * Radius;
public Circle(double radius)
{
Radius = radius;
}
}
Max Line Length Analyzer
Diagnostic ID: STAR_MAX_LINE_LENGTH
Category: Formatting
Severity: Warning
Description: Lines should not exceed a maximum allowed length to maintain code readability. The default maximum is 80 characters, but this can be configured.
Configuration
The analyzer can be configured using an .editorconfig file:
[*.cs]
dotnet_diagnostic.STAR_MAX_LINE_LENGTH.max_line_length = 100
Incorrect Code Examples
// ❌ Line exceeds maximum length (assuming 80 character limit)
public class VeryLongClassNameThatExceedsTheMaximumAllowedLineLengthAndShouldBeRefactored
{
public void VeryLongMethodNameThatAlsoExceedsTheMaximumAllowedLineLengthAndShouldBeRefactored(string veryLongParameterNameThatExceedsTheMaximumAllowedLineLength)
{
var veryLongVariableNameThatExceedsTheMaximumAllowedLineLength = "This is a very long string literal that exceeds the maximum allowed line length";
}
}
Correct Code Examples
// ✅ Lines within maximum length
public class Person
{
public void ProcessPerson(
string name,
int age,
string email)
{
var person = new Person
{
Name = name,
Age = age,
Email = email
};
// Process the person
ProcessPersonData(person);
}
private void ProcessPersonData(Person person)
{
// Implementation here
}
}
// ✅ Using line breaks for long lines
public class Configuration
{
public void UpdateConfiguration(
string connectionString,
bool isEnabled,
int timeout,
string databaseName)
{
var config = new Config
{
ConnectionString = connectionString,
IsEnabled = isEnabled,
Timeout = timeout,
DatabaseName = databaseName
};
}
}
Pre-Increment Analyzer
Diagnostic ID: STAR_NO_PRE_INCREMENT
Category: Syntax
Severity: Warning
Description: Pre-increment operations (++variable) are not allowed outside of for loop headers. This analyzer enforces restricted usage of pre-increment operators.
Incorrect Code Examples
// ❌ Pre-increment outside for loop
public class Example
{
public void ProcessData()
{
int counter = 0;
++counter; // Not allowed
var result = ProcessValue(++counter); // Not allowed
}
public int GetNextValue(int current)
{
return ++current; // Not allowed
}
}
Correct Code Examples
// ✅ Pre-increment in for loop header
public class Example
{
public void ProcessData()
{
for (int i = 0; i < 10; ++i) // Allowed in for loop
{
ProcessItem(i);
}
}
public void ProcessArray(int[] items)
{
for (int i = 0; i < items.Length; ++i) // Allowed in for loop
{
ProcessItem(items[i]);
}
}
// ✅ Alternative: use post-increment or simple addition
public void AlternativeApproach()
{
int counter = 0;
counter++; // Post-increment is allowed
counter += 1; // Simple addition is allowed
var result = ProcessValue(counter + 1); // Expression is allowed
}
}
Post-Increment Analyzer
Diagnostic ID: STAR_NO_POST_INCREMENT
Category: Syntax
Severity: Warning
Description: Post-increment operations (variable++) are not allowed outside of for loop headers. This analyzer enforces restricted usage of post-increment operators.
Incorrect Code Examples
// ❌ Post-increment outside for loop
public class Example
{
public void ProcessData()
{
int counter = 0;
counter++; // Not allowed
var result = ProcessValue(counter++); // Not allowed
}
public int GetNextValue(int current)
{
return current++; // Not allowed
}
}
Correct Code Examples
// ✅ Post-increment in for loop header
public class Example
{
public void ProcessData()
{
for (int i = 0; i < 10; i++) // Allowed in for loop
{
ProcessItem(i);
}
}
public void ProcessArray(int[] items)
{
for (int i = 0; i < items.Length; i++) // Allowed in for loop
{
ProcessItem(items[i]);
}
}
// ✅ Alternative: use simple addition
public void AlternativeApproach()
{
int counter = 0;
counter += 1; // Simple addition is allowed
var result = ProcessValue(counter + 1); // Expression is allowed
}
}
Pre-Decrement Analyzer
Diagnostic ID: STAR_NO_PRE_DECREMENT
Category: Syntax
Severity: Warning
Description: Pre-decrement operations (--variable) are not allowed outside of for loop headers. This analyzer enforces restricted usage of pre-decrement operators.
Incorrect Code Examples
// ❌ Pre-decrement outside for loop
public class Example
{
public void ProcessData()
{
int counter = 10;
--counter; // Not allowed
var result = ProcessValue(--counter); // Not allowed
}
public int GetPreviousValue(int current)
{
return --current; // Not allowed
}
}
Correct Code Examples
// ✅ Pre-decrement in for loop header
public class Example
{
public void ProcessData()
{
for (int i = 10; i > 0; --i) // Allowed in for loop
{
ProcessItem(i);
}
}
public void ProcessArrayReverse(int[] items)
{
for (int i = items.Length - 1; i >= 0; --i) // Allowed in for loop
{
ProcessItem(items[i]);
}
}
// ✅ Alternative: use simple subtraction
public void AlternativeApproach()
{
int counter = 10;
counter -= 1; // Simple subtraction is allowed
var result = ProcessValue(counter - 1); // Expression is allowed
}
}
Post-Decrement Analyzer
Diagnostic ID: STAR_NO_POST_DECREMENT
Category: Syntax
Severity: Warning
Description: Post-decrement operations (variable--) are not allowed outside of for loop headers. This analyzer enforces restricted usage of post-decrement operators.
Incorrect Code Examples
// ❌ Post-decrement outside for loop
public class Example
{
public void ProcessData()
{
int counter = 10;
counter--; // Not allowed
var result = ProcessValue(counter--); // Not allowed
}
public int GetPreviousValue(int current)
{
return current--; // Not allowed
}
}
Correct Code Examples
// ✅ Post-decrement in for loop header
public class Example
{
public void ProcessData()
{
for (int i = 10; i > 0; i--) // Allowed in for loop
{
ProcessItem(i);
}
}
public void ProcessArrayReverse(int[] items)
{
for (int i = items.Length - 1; i >= 0; i--) // Allowed in for loop
{
ProcessItem(items[i]);
}
}
// ✅ Alternative: use simple subtraction
public void AlternativeApproach()
{
int counter = 10;
counter -= 1; // Simple subtraction is allowed
var result = ProcessValue(counter - 1); // Expression is allowed
}
}
No Nullable Null Check Analyzer
Diagnostic ID: STAR_NO_NULLABLE_CHECKS_FOR_NON_NULL_TYPES
Category: Usage
Severity: Warning
Description: Types marked as non-nullable cannot be null, so checking them for null is unnecessary. This analyzer detects redundant null checks on non-nullable types.
Incorrect Code Examples
// ❌ Unnecessary null checks on non-nullable types
public class Example
{
public void ProcessPerson(Person person) // Person is non-nullable
{
if (person == null) // Unnecessary check
{
return;
}
// Process person
}
public void ProcessString(string text) // String is non-nullable
{
if (text != null) // Unnecessary check
{
ProcessText(text);
}
}
public void ProcessInt(int number) // int is non-nullable
{
if (number == null) // Unnecessary check
{
return;
}
}
}
Correct Code Examples
// ✅ No null checks on non-nullable types
public class Example
{
public void ProcessPerson(Person person) // Person is non-nullable
{
// No null check needed - person cannot be null
ProcessPersonData(person);
}
public void ProcessString(string text) // String is non-nullable
{
// No null check needed - text cannot be null
ProcessText(text);
}
public void ProcessInt(int number) // int is non-nullable
{
// No null check needed - number cannot be null
ProcessNumber(number);
}
}
// ✅ Null checks on nullable types
public class ExampleWithNullable
{
public void ProcessPerson(Person? person) // Person? is nullable
{
if (person == null) // Necessary check
{
return;
}
ProcessPersonData(person);
}
public void ProcessString(string? text) // string? is nullable
{
if (text != null) // Necessary check
{
ProcessText(text);
}
}
public void ProcessInt(int? number) // int? is nullable
{
if (number.HasValue) // Necessary check
{
ProcessNumber(number.Value);
}
}
}
No Throw Analyzer
Diagnostic ID: STAR_NO_THROW
Category: Syntax
Severity: Warning
Description: Throw statements and expressions are not allowed anywhere in the code. This analyzer enforces a strict no-exception policy, requiring all error handling to be done through return values, error objects, or other non-exception mechanisms.
Incorrect Code Examples
// ❌ Throw statement in method
public class Example
{
public void ProcessData(string input)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentException("Input cannot be null or empty"); // Not allowed
// Process input
}
public string GetValue(int index)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index)); // Not allowed
return "value";
}
}
// ❌ Throw expression in property initializer
public class Configuration
{
public string ConnectionString { get; } =
throw new InvalidOperationException("Connection string not configured"); // Not allowed
}
// ❌ Throw statement in constructor
public class Person
{
public Person(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be null or empty", nameof(name)); // Not allowed
Name = name;
}
public string Name { get; }
}
// ❌ Throw statement in catch block
public void HandleException()
{
try
{
// Some operation
}
catch (Exception ex)
{
throw new CustomException("Custom error", ex); // Not allowed
}
}
// ❌ Throw expression in conditional operator
public string GetResult(bool condition)
{
return condition ? "success" : throw new Exception("Condition failed"); // Not allowed
}
Correct Code Examples
// ✅ Return error result instead of throwing
public class Example
{
public Result<string> ProcessData(string input)
{
if (string.IsNullOrEmpty(input))
return Result<string>.Failure("Input cannot be null or empty");
// Process input
return Result<string>.Success("Processed successfully");
}
public Result<string> GetValue(int index)
{
if (index < 0)
return Result<string>.Failure($"Index {index} is out of range");
return Result<string>.Success("value");
}
}
// ✅ Use default values instead of throw expressions
public class Configuration
{
public string ConnectionString { get; } = "DefaultConnectionString";
}
// ✅ Validate in constructor without throwing
public class Person
{
public Person(string name)
{
if (string.IsNullOrEmpty(name))
{
Name = "DefaultName"; // Use default value
return;
}
Name = name;
}
public string Name { get; }
}
// ✅ Handle exceptions without re-throwing
public void HandleException()
{
try
{
// Some operation
}
catch (Exception ex)
{
LogError(ex); // Log the error
// Handle gracefully without throwing
}
}
// ✅ Use conditional logic instead of throw expressions
public string GetResult(bool condition)
{
return condition ? "success" : "failure"; // Return value instead of throwing
}
// ✅ Use Result pattern for error handling
public class Result<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public string Error { get; }
private Result(bool isSuccess, T value, string error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
public static Result<T> Success(T value) => new Result<T>(true, value, null);
public static Result<T> Failure(string error) => new Result<T>(false, default, error);
}
Summary
The StrictAnalyzers project provides a comprehensive set of analyzers that enforce strict coding standards and best practices. These analyzers help maintain:
- Immutability: Ensuring classes and records are immutable for thread safety
- Code Quality: Enforcing consistent formatting and syntax rules
- Best Practices: Promoting the use of modern C# features like records and required members
- Performance: Restricting potentially problematic operations like assignments and increment/decrement outside of loops
Each analyzer can be configured and customized to fit your project's specific requirements. The analyzers integrate seamlessly with Visual Studio and other IDEs that support Roslyn analyzers.
For more information about configuring these analyzers or contributing to the project, please refer to the project documentation and source code.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.3.0 | 105 | 1/28/2026 |
| 1.2.11 | 149 | 1/1/2026 |
| 1.2.10 | 92 | 12/31/2025 |
| 1.2.9 | 98 | 12/31/2025 |
| 1.2.8 | 96 | 12/31/2025 |
| 1.2.7 | 96 | 12/31/2025 |
| 1.2.6 | 99 | 12/31/2025 |
| 1.2.5 | 99 | 12/30/2025 |
| 1.2.4 | 99 | 12/30/2025 |
| 1.1.4 | 155 | 12/26/2025 |
| 1.1.3 | 554 | 12/9/2025 |
| 1.1.2 | 220 | 11/9/2025 |
| 1.1.1 | 220 | 11/9/2025 |
| 1.1.0 | 147 | 11/9/2025 |
| 1.0.7 | 157 | 11/9/2025 |
| 1.0.6 | 155 | 11/8/2025 |
| 1.0.5 | 191 | 10/6/2025 |
| 1.0.4 | 208 | 10/6/2025 |
| 1.0.3 | 190 | 10/6/2025 |
| 1.0.2 | 180 | 10/5/2025 |