|
| 1 | +# ECS1300: Use Proper Initialization for Static Class Members |
| 2 | + |
| 3 | +## Cause |
| 4 | + |
| 5 | +Static fields initialized with complex expressions or method calls that might throw exceptions can cause runtime errors during type initialization. These exceptions occur before any static constructors or error-handling mechanisms can intercept them, potentially crashing the application. |
| 6 | + |
| 7 | +## Rule Description |
| 8 | + |
| 9 | +This rule identifies static fields that are initialized directly with complex expressions, method calls, or operations that could throw exceptions. Initializing static fields in this manner can lead to unhandled exceptions during the type's initialization phase, making debugging difficult and potentially causing the application to terminate unexpectedly. |
| 10 | + |
| 11 | +By moving complex initializations into a static constructor or using `Lazy<T>`, you can control the timing of the initialization and handle any exceptions appropriately. This approach enhances the reliability and maintainability of your code. |
| 12 | + |
| 13 | +## How to fix violations |
| 14 | + |
| 15 | +- **Use a Static Constructor**: Move the complex initialization logic into a static constructor where you can handle exceptions and control execution flow. |
| 16 | + |
| 17 | +```csharp |
| 18 | +public class MyClass |
| 19 | +{ |
| 20 | + private static readonly string ConfigValue; |
| 21 | + |
| 22 | + static MyClass() |
| 23 | + { |
| 24 | + ConfigValue = LoadConfigValue(); |
| 25 | + } |
| 26 | + |
| 27 | + private static string LoadConfigValue() |
| 28 | + { |
| 29 | + // Complex initialization logic |
| 30 | + return System.IO.File.ReadAllText("config.txt"); |
| 31 | + } |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +- **Use `Lazy<T>` for Lazy Initialization**: Wrap the initialization logic in a `Lazy<T>` object to defer execution until the value is needed. |
| 36 | + |
| 37 | +```csharp |
| 38 | +public class MyClass |
| 39 | +{ |
| 40 | + private static readonly Lazy<string> ConfigValue = new Lazy<string>(LoadConfigValue); |
| 41 | + |
| 42 | + private static string LoadConfigValue() |
| 43 | + { |
| 44 | + // Complex initialization logic |
| 45 | + return System.IO.File.ReadAllText("config.txt"); |
| 46 | + } |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +## Extending the Rule with Safe Methods |
| 51 | + |
| 52 | +### Customizing Safe Methods using Configuration |
| 53 | + |
| 54 | +The analyzer allows you to specify additional methods that should be considered safe for static initialization. You can extend the list of safe methods by using the `dotnet_diagnostic.ECS1300.safe_methods` option in an EditorConfig file. |
| 55 | + |
| 56 | +### How to Configure Additional Safe Methods |
| 57 | + |
| 58 | +1. Create or Update an EditorConfig File: Add or modify an `.editorconfig` file in your project directory. If you don't have one, you can create it at the root of your project. |
| 59 | +2. Add the `safe_items` Option: Use the `dotnet_diagnostic.ECS1300.safe_items` key to specify a comma-separated list of fully qualified method and/or property names that you want to treat as safe. |
| 60 | + |
| 61 | +```ini |
| 62 | +[*.cs] |
| 63 | +dotnet_diagnostic.ECS1300.safe_items = System.MySafeClass.MySafeMethod, System.AnotherClass.AnotherSafeMethod |
| 64 | +``` |
| 65 | + |
| 66 | +>Note: Ensure that the method and property names are fully qualified, including the namespace and class name. |
| 67 | +
|
| 68 | +#### Example Configuration |
| 69 | + |
| 70 | +Suppose you have methods and properties in your codebase that you know are safe for static initialization: |
| 71 | + |
| 72 | +- `Contoso.Utilities.GetDefaultSettings` |
| 73 | +- `Contoso.Constants.GetMaxValue` |
| 74 | +- `Contoso.RecordManager.MaxValue` |
| 75 | + |
| 76 | +You can add the fully qualified symbol to the safe list: |
| 77 | + |
| 78 | +```ini |
| 79 | +[*.cs] |
| 80 | +dotnet_diagnostic.ECS1300.safe_methods = Contoso.Utilities.GetDefaultSettings, Contoso.Constants.GetMaxValue, Contoso.RecordManager.MaxValue |
| 81 | +``` |
| 82 | + |
| 83 | +#### Effect on the Analyzer |
| 84 | +By configuring these methods and properties as safe, the analyzer will no longer report diagnostics for static fields initialized using them: |
| 85 | + |
| 86 | +```csharp |
| 87 | +public class MyClass |
| 88 | +{ |
| 89 | + // No diagnostic will be reported for this initialization |
| 90 | + private static readonly Settings DefaultSettings = Utilities.GetDefaultSettings(); |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +### When to Use This Option |
| 95 | + |
| 96 | +- **Third-Party Libraries**: If you use methods from external libraries that you know are safe but are not included in the default safe methods list. |
| 97 | +- **Custom Utility Methods**: For utility methods in your codebase that are deterministic and exception-safe. |
| 98 | +- **Performance Considerations**: When you prefer direct initialization for performance reasons and are confident in the safety of the methods used. |
| 99 | + |
| 100 | +### Precautions |
| 101 | + |
| 102 | +- **Ensure Safety**: Only add methods that are guaranteed not to throw exceptions during static initialization. |
| 103 | +- **Fully Qualified Names**: Use the full namespace and class names to avoid conflicts and ensure the correct methods are treated as safe. |
| 104 | + |
| 105 | +## When to suppress warnings |
| 106 | + |
| 107 | +You may choose to suppress this warning if: |
| 108 | + |
| 109 | +- The static field initialization is guaranteed not to throw exceptions. |
| 110 | +- The initialization logic is simple and does not involve method calls or expressions that could fail. |
| 111 | +- You have thoroughly tested the initialization and are confident in its safety. |
| 112 | + |
| 113 | +### Suppress a warning |
| 114 | + |
| 115 | +If you just want to suppress a single violation, add preprocessor directives to your source file to disable and then re-enable the rule. |
| 116 | + |
| 117 | +```csharp |
| 118 | +#pragma warning disable ECS1300 |
| 119 | +// The code that's violating the rule |
| 120 | +#pragma warning restore ECS1300 |
| 121 | +``` |
| 122 | + |
| 123 | +To disable the rule for a file, folder, or project, set its severity to none in the [configuration file](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files). |
| 124 | + |
| 125 | +```ini |
| 126 | +[*.cs] |
| 127 | +dotnet_diagnostic.ECS1300.severity = none |
| 128 | +``` |
| 129 | + |
| 130 | +## Example of a violation |
| 131 | + |
| 132 | +### Description |
| 133 | + |
| 134 | +A static field is initialized using a method that reads from a file. This method call could throw an `IOException` if the file does not exist or is inaccessible. |
| 135 | + |
| 136 | +### Code |
| 137 | + |
| 138 | +```csharp |
| 139 | +public class MyClass |
| 140 | +{ |
| 141 | + private static readonly string ConfigValue = LoadConfigValue(); |
| 142 | + |
| 143 | + private static string LoadConfigValue() |
| 144 | + { |
| 145 | + return System.IO.File.ReadAllText("config.txt"); |
| 146 | + } |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +## Example of how to fix |
| 151 | + |
| 152 | +### Description |
| 153 | + |
| 154 | +```csharp |
| 155 | +public class MyClass |
| 156 | +{ |
| 157 | + private static readonly string ConfigValue; |
| 158 | + |
| 159 | + static MyClass() |
| 160 | + { |
| 161 | + try |
| 162 | + { |
| 163 | + ConfigValue = LoadConfigValue(); |
| 164 | + } |
| 165 | + catch (IOException ex) |
| 166 | + { |
| 167 | + // Handle exception, possibly logging or setting a default value |
| 168 | + ConfigValue = "DefaultConfig"; |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + private static string LoadConfigValue() |
| 173 | + { |
| 174 | + return System.IO.File.ReadAllText("config.txt"); |
| 175 | + } |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +Alternatively, use `Lazy<T>` to defer the initialization: |
| 180 | + |
| 181 | +```csharp |
| 182 | +public class MyClass |
| 183 | +{ |
| 184 | + private static readonly Lazy<string> ConfigValue = new Lazy<string>(() => |
| 185 | + { |
| 186 | + return System.IO.File.ReadAllText("config.txt"); |
| 187 | + }); |
| 188 | + |
| 189 | + // Access ConfigValue.Value when needed |
| 190 | +} |
| 191 | +``` |
0 commit comments