Skip to content

Commit 5bf47f7

Browse files
committed
Drop immutability which doesn't add any value
Config writing is such a rare occurrence (compared with reading) that the additional complexity of making the config/document immutable doesn't bring much value. Quite the contrary, it's a source of hard to detect bugs. The pattern of returning the same object for chaining is preserved (so the API is backs-compat), and the mutability is compatible with how ServiceCollection works, for example. The chaining is just for convenience.
1 parent 24bd971 commit 5bf47f7

File tree

7 files changed

+105
-79
lines changed

7 files changed

+105
-79
lines changed

docs/api/index.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,9 @@ configuration level to use for persisting the value, by passing a `ConfigLevel`:
9696
//[vs "alias"]
9797
// comexp = run|community|exp
9898
99-
config = config.AddString("vs", "alias", "comexp", "run|community|exp", ConfigLevel.Global);
99+
config.AddString("vs", "alias", "comexp", "run|community|exp", ConfigLevel.Global);
100100
```
101101

102-
> IMPORTANT: the Config API is immutable, so if you make changes, you should update your reference
103-
> to the newly updated Config, otherwise, subsequent changes would override prior ones.
104-
105102
You can explore the entire API in the [docs site](https://dotnetconfig.org/api/).
106103

107104
### Microsoft.Extensions.Configuration

readme.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,9 @@ configuration level to use for persisting the value, by passing a `ConfigLevel`:
324324
//[vs "alias"]
325325
// comexp = run|community|exp
326326
327-
config = config.AddString("vs", "alias", "comexp", "run|community|exp", ConfigLevel.Global);
327+
config.AddString("vs", "alias", "comexp", "run|community|exp", ConfigLevel.Global);
328328
```
329329

330-
> IMPORTANT: the Config API is immutable, so if you make changes, you should update your reference
331-
> to the newly updated Config, otherwise, subsequent changes would override prior ones.
332-
333330
You can explore the entire API in the [docs site](https://dotnetconfig.org/api/).
334331

335332
### Microsoft.Extensions.Configuration

src/Config.Tests/ConfigTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,21 @@ public void when_setting_variable_then_uses_tab()
262262
Assert.Equal('\t', line[0]);
263263
}
264264

265+
[Fact]
266+
public void when_setting_muliplevariables_then_can_reuse_instance()
267+
{
268+
var file = Path.GetTempFileName();
269+
var config = Config.Build(file);
270+
271+
config.SetString("section", "subsection", "foo", "bar");
272+
config.SetString("section", "subsection", "bar", "baz");
273+
274+
var saved = Config.Build(file);
275+
276+
Assert.Equal("bar", saved.GetString("section", "subsection", "foo"));
277+
Assert.Equal("baz", saved.GetString("section", "subsection", "bar"));
278+
}
279+
265280
[Fact]
266281
public void when_setting_global_variable_then_writes_global_file()
267282
{

src/Config/Config.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ public static Config Build(ConfigLevel store) =>
159159
/// </summary>
160160
public ConfigLevel? Level =>
161161
this is AggregateConfig ? null :
162-
FilePath == GlobalLocation ? (ConfigLevel?)ConfigLevel.Global :
163-
FilePath == SystemLocation ? (ConfigLevel?)ConfigLevel.System :
164-
FilePath.EndsWith(UserExtension) ? (ConfigLevel?)ConfigLevel.Local : null;
162+
FilePath == GlobalLocation ? ConfigLevel.Global :
163+
FilePath == SystemLocation ? ConfigLevel.System :
164+
FilePath.EndsWith(UserExtension) ? ConfigLevel.Local : null;
165165

166166
/// <summary>
167167
/// Gets the section and optional subsection from the configuration.

src/Config/Config.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ Usage:
77
var config = Config.Build();
88
var value = config.GetString("section", "subsection", "variable");
99

10-
// Setting values, Config is immutable, so chain calls and update var
11-
config = config
10+
// Setting values
11+
config
1212
.SetString("section", "subsection", "variable", value)
1313
.SetBoolean("section", "subsection", "enabled", true);
1414
</Description>

src/Config/ConfigDocument.cs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ record ConfigDocument : IEnumerable<ConfigEntry>
3838

3939
public ConfigLevel? Level { get; }
4040

41-
internal ImmutableList<Line> Lines { get; init; } = ImmutableList<Line>.Empty;
41+
internal ImmutableList<Line> Lines { get; private set; } = ImmutableList<Line>.Empty;
4242

4343
public ConfigDocument Save()
4444
{
@@ -99,7 +99,8 @@ public ConfigDocument Add(string section, string? subsection, string name, strin
9999
lines = lines.Insert(index, Line.CreateVariable(filePath, index, sectionLine.Section, sectionLine.Subsection, name, value));
100100
}
101101

102-
return this with { Lines = lines };
102+
Lines = lines;
103+
return this;
103104
}
104105

105106
public ConfigDocument Set(string section, string? subsection, string name, string? value = null, ValueMatcher? valueMatcher = null)
@@ -119,7 +120,8 @@ public ConfigDocument Set(string section, string? subsection, string name, strin
119120

120121
if (variable != null)
121122
{
122-
return this with { Lines = Lines.Replace(variable, variable.WithValue(value)) };
123+
Lines = Lines.Replace(variable, variable.WithValue(value));
124+
return this;
123125
}
124126
else
125127
{
@@ -143,7 +145,9 @@ public ConfigDocument Unset(string section, string? subsection, string name)
143145
var variable = variables.FirstOrDefault();
144146
if (variable != null)
145147
{
146-
return (this with { Lines = Lines.Remove(variable) }).CleanupSection(section, subsection);
148+
Lines = Lines.Remove(variable);
149+
CleanupSection(section, subsection);
150+
return this;
147151
}
148152

149153
return this;
@@ -164,7 +168,8 @@ public ConfigDocument SetAll(string section, string? subsection, string name, st
164168
lines = lines.Replace(variable, variable.WithValue(value));
165169
}
166170

167-
return this with { Lines = lines };
171+
Lines = lines;
172+
return this;
168173
}
169174

170175
public ConfigDocument UnsetAll(string section, string? subsection, string name, ValueMatcher? valueMatcher = null)
@@ -185,7 +190,9 @@ public ConfigDocument UnsetAll(string section, string? subsection, string name,
185190
lines = lines.Remove(variable);
186191
}
187192

188-
return (this with { Lines = lines }).CleanupSection(section, subsection);
193+
Lines = lines;
194+
CleanupSection(section, subsection);
195+
return this;
189196
}
190197

191198
public ConfigDocument RemoveSection(string section, string? subsection = null)
@@ -216,7 +223,8 @@ public ConfigDocument RemoveSection(string section, string? subsection = null)
216223
while (lines.Count > 0 && lines[^1].Kind == LineKind.None)
217224
lines = lines.RemoveAt(lines.Count - 1);
218225

219-
return this with { Lines = lines };
226+
Lines = lines;
227+
return this;
220228
}
221229

222230
public ConfigDocument RenameSection(string oldSection, string? oldSubsection, string newSection, string? newSubsection)
@@ -246,7 +254,8 @@ public ConfigDocument RenameSection(string oldSection, string? oldSubsection, st
246254
}
247255
}
248256

249-
return this with { Lines = lines };
257+
Lines = lines;
258+
return this;
250259
}
251260

252261
/// <summary>
@@ -266,7 +275,8 @@ ConfigDocument CleanupSection(string section, string? subsection)
266275
while (index < lines.Count && lines[index].Kind == LineKind.None)
267276
lines = lines.RemoveAt(index);
268277

269-
return this with { Lines = lines };
278+
Lines = lines;
279+
return this;
270280
}
271281
}
272282

src/Config/FileConfig.cs

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,33 @@ public override Config AddBoolean(string section, string? subsection, string var
2020
if (value)
2121
{
2222
// Shortcut notation.
23-
return new FileConfig(FilePath,
24-
document.Add(section, subsection, variable, null)
25-
.Save());
23+
document.Add(section, subsection, variable, null).Save();
2624
}
2725
else
2826
{
29-
return new FileConfig(FilePath,
30-
document.Add(section, subsection, variable, "false")
31-
.Save());
27+
document.Add(section, subsection, variable, "false").Save();
3228
}
29+
30+
return this;
3331
}
3432

3533
public override Config AddDateTime(string section, string? subsection, string variable, DateTime value)
36-
=> new FileConfig(FilePath, document
37-
.Add(section, subsection, variable, value.ToString("O"))
38-
.Save());
34+
{
35+
document.Add(section, subsection, variable, value.ToString("O")).Save();
36+
return this;
37+
}
3938

4039
public override Config AddNumber(string section, string? subsection, string variable, long value)
41-
=> new FileConfig(FilePath, document
42-
.Add(section, subsection, variable, value.ToString())
43-
.Save());
40+
{
41+
document.Add(section, subsection, variable, value.ToString()).Save();
42+
return this;
43+
}
4444

4545
public override Config AddString(string section, string? subsection, string variable, string value)
46-
=> new FileConfig(FilePath, document
47-
.Add(section, subsection, variable, value)
48-
.Save());
46+
{
47+
document.Add(section, subsection, variable, value).Save();
48+
return this;
49+
}
4950

5051
public override IEnumerable<ConfigEntry> GetAll(string section, string? subsection, string variable, string? valueRegex)
5152
=> document.GetAll(section, subsection, variable, valueRegex);
@@ -69,78 +70,82 @@ public override IEnumerable<ConfigEntry> GetRegex(string nameRegex, string? valu
6970
}
7071

7172
public override Config RemoveSection(string section, string? subsection)
72-
=> new FileConfig(FilePath, document
73-
.RemoveSection(section, subsection)
74-
.Save());
73+
{
74+
document.RemoveSection(section, subsection).Save();
75+
return this;
76+
}
7577

7678
public override Config RenameSection(string oldSection, string? oldSubsection, string newSection, string? newSubsection)
77-
=> new FileConfig(FilePath, document
78-
.RenameSection(oldSection, oldSubsection, newSection, newSubsection)
79-
.Save());
79+
{
80+
document.RenameSection(oldSection, oldSubsection, newSection, newSubsection).Save();
81+
return this;
82+
}
8083

8184
public override Config SetAllBoolean(string section, string? subsection, string variable, bool value, string? valueRegex)
8285
{
8386
if (value)
8487
{
8588
// Shortcut notation.
86-
return new FileConfig(FilePath, document
87-
.SetAll(section, subsection, variable, null, valueRegex)
88-
.Save());
89+
document.SetAll(section, subsection, variable, null, valueRegex).Save();
8990
}
9091
else
9192
{
92-
return new FileConfig(FilePath, document
93-
.SetAll(section, subsection, variable, "false", valueRegex)
94-
.Save());
93+
document.SetAll(section, subsection, variable, "false", valueRegex).Save();
9594
}
95+
96+
return this;
9697
}
9798

9899
public override Config SetAllDateTime(string section, string? subsection, string variable, DateTime value, string? valueRegex)
99-
=> new FileConfig(FilePath, document
100-
.SetAll(section, subsection, variable, value.ToString("O"), valueRegex)
101-
.Save());
100+
{
101+
document.SetAll(section, subsection, variable, value.ToString("O"), valueRegex).Save();
102+
return this;
103+
}
102104

103105
public override Config SetAllNumber(string section, string? subsection, string variable, long value, string? valueRegex)
104-
=> new FileConfig(FilePath, document
105-
.SetAll(section, subsection, variable, value.ToString(), valueRegex)
106-
.Save());
106+
{
107+
document.SetAll(section, subsection, variable, value.ToString(), valueRegex).Save();
108+
return this;
109+
}
107110

108111
public override Config SetAllString(string section, string? subsection, string variable, string value, string? valueRegex)
109-
=> new FileConfig(FilePath, document
110-
.SetAll(section, subsection, variable, value, valueRegex)
111-
.Save());
112+
{
113+
document.SetAll(section, subsection, variable, value, valueRegex).Save();
114+
return this;
115+
}
112116

113117
public override Config SetBoolean(string section, string? subsection, string variable, bool value, string? valueRegex)
114118
{
115119
if (value)
116120
{
117121
// Shortcut notation.
118-
return new FileConfig(FilePath, document
119-
.Set(section, subsection, variable, null, valueRegex)
120-
.Save());
122+
document.Set(section, subsection, variable, null, valueRegex).Save();
121123
}
122124
else
123125
{
124-
return new FileConfig(FilePath, document
125-
.Set(section, subsection, variable, "false", valueRegex)
126-
.Save());
126+
document.Set(section, subsection, variable, "false", valueRegex).Save();
127127
}
128+
129+
return this;
128130
}
129131

130132
public override Config SetDateTime(string section, string? subsection, string variable, DateTime value, string? valueRegex)
131-
=> new FileConfig(FilePath, document
132-
.Set(section, subsection, variable, value.ToString("O"), valueRegex)
133-
.Save());
133+
{
134+
document.Set(section, subsection, variable, value.ToString("O"), valueRegex).Save();
135+
return this;
136+
}
134137

135138
public override Config SetNumber(string section, string? subsection, string variable, long value, string? valueRegex)
136-
=> new FileConfig(FilePath, document
137-
.Set(section, subsection, variable, value.ToString(), valueRegex)
138-
.Save());
139+
{
140+
document.Set(section, subsection, variable, value.ToString(), valueRegex).Save();
141+
return this;
142+
}
139143

140144
public override Config SetString(string section, string? subsection, string variable, string value, string? valueRegex)
141-
=> new FileConfig(FilePath, document
142-
.Set(section, subsection, variable, value, valueRegex)
143-
.Save());
145+
{
146+
document.Set(section, subsection, variable, value, valueRegex).Save();
147+
return this;
148+
}
144149

145150
public override bool TryGetBoolean(string section, string? subsection, string variable, out bool value)
146151
{
@@ -211,14 +216,16 @@ public override bool TryGetString(string section, string? subsection, string var
211216
}
212217

213218
public override Config Unset(string section, string? subsection, string variable)
214-
=> new FileConfig(FilePath, document
215-
.Unset(section, subsection, variable)
216-
.Save());
219+
{
220+
document.Unset(section, subsection, variable).Save();
221+
return this;
222+
}
217223

218224
public override Config UnsetAll(string section, string? subsection, string variable, string? valueMatcher)
219-
=> new FileConfig(FilePath, document
220-
.UnsetAll(section, subsection, variable, valueMatcher)
221-
.Save());
225+
{
226+
document.UnsetAll(section, subsection, variable, valueMatcher).Save();
227+
return this;
228+
}
222229

223230
protected override IEnumerable<ConfigEntry> GetEntries() => document;
224231

0 commit comments

Comments
 (0)