Skip to content

Commit a130fec

Browse files
committed
env: avoid virtual member call in ctor; all refresh
We had a virtual member call in the constructor of the various IEnvironment implementations, which is a code smell. Instead, lazily populate the `_variables` cache dictionary, and add an explict `Refresh` method to refresh the cache. We call `Refresh` in the `SetEnvironmentVariable` method for convenience. For testing we simply pipe through the pre-computed variables in the `internal` constructor.
1 parent c12409f commit a130fec

File tree

5 files changed

+57
-23
lines changed

5 files changed

+57
-23
lines changed

src/shared/Core/EnvironmentBase.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,45 @@ public interface IEnvironment
5353
/// <param name="target">Target level of environment variable to set (Machine, Process, or User).</param>
5454
void SetEnvironmentVariable(string variable, string value,
5555
EnvironmentVariableTarget target = EnvironmentVariableTarget.Process);
56+
57+
/// <summary>
58+
/// Refresh the current process environment variables. See <see cref="Variables"/>.
59+
/// </summary>
60+
/// <remarks>This is automatically called after <see cref="SetEnvironmentVariable"/>.</remarks>
61+
void Refresh();
5662
}
5763

5864
public abstract class EnvironmentBase : IEnvironment
5965
{
66+
private IReadOnlyDictionary<string, string> _variables;
67+
6068
protected EnvironmentBase(IFileSystem fileSystem)
6169
{
6270
EnsureArgument.NotNull(fileSystem, nameof(fileSystem));
63-
6471
FileSystem = fileSystem;
6572
}
6673

67-
public IReadOnlyDictionary<string, string> Variables { get; protected set; }
74+
internal EnvironmentBase(IFileSystem fileSystem, IReadOnlyDictionary<string, string> variables)
75+
: this(fileSystem)
76+
{
77+
EnsureArgument.NotNull(variables, nameof(variables));
78+
_variables = variables;
79+
}
80+
81+
public IReadOnlyDictionary<string, string> Variables
82+
{
83+
get
84+
{
85+
// Variables are lazily loaded
86+
if (_variables is null)
87+
{
88+
Refresh();
89+
}
90+
91+
Debug.Assert(_variables != null);
92+
return _variables;
93+
}
94+
}
6895

6996
protected IFileSystem FileSystem { get; }
7097

@@ -126,9 +153,22 @@ internal virtual bool TryLocateExecutable(string program, ICollection<string> pa
126153
public void SetEnvironmentVariable(string variable, string value,
127154
EnvironmentVariableTarget target = EnvironmentVariableTarget.Process)
128155
{
129-
if (Variables.Keys.Contains(variable)) return;
156+
// Don't bother setting the variable if it already has the same value
157+
if (Variables.TryGetValue(variable, out var currentValue) &&
158+
StringComparer.Ordinal.Equals(currentValue, value))
159+
{
160+
return;
161+
}
162+
130163
Environment.SetEnvironmentVariable(variable, value, target);
131-
Variables = GetCurrentVariables();
164+
165+
// Immediately refresh the variables so that the new value is available to callers using IEnvironment
166+
Refresh();
167+
}
168+
169+
public void Refresh()
170+
{
171+
_variables = GetCurrentVariables();
132172
}
133173

134174
protected abstract IReadOnlyDictionary<string, string> GetCurrentVariables();

src/shared/Core/Interop/MacOS/MacOSEnvironment.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ public MacOSEnvironment(IFileSystem fileSystem)
1515
: base(fileSystem) { }
1616

1717
internal MacOSEnvironment(IFileSystem fileSystem, IReadOnlyDictionary<string, string> variables)
18-
: base(fileSystem)
19-
{
20-
EnsureArgument.NotNull(variables, nameof(variables));
21-
Variables = variables;
22-
}
18+
: base(fileSystem, variables) { }
2319

2420
public override bool TryLocateExecutable(string program, out string path)
2521
{
@@ -35,4 +31,4 @@ public override bool TryLocateExecutable(string program, out string path)
3531
return TryLocateExecutable(program, _pathsToIgnore, out path);
3632
}
3733
}
38-
}
34+
}

src/shared/Core/Interop/Posix/PosixEnvironment.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@ namespace GitCredentialManager.Interop.Posix
77
public class PosixEnvironment : EnvironmentBase
88
{
99
public PosixEnvironment(IFileSystem fileSystem)
10-
: this(fileSystem, null) { }
10+
: base(fileSystem) { }
1111

1212
internal PosixEnvironment(IFileSystem fileSystem, IReadOnlyDictionary<string, string> variables)
13-
: base(fileSystem)
14-
{
15-
Variables = variables ?? GetCurrentVariables();
16-
}
13+
: base(fileSystem, variables) { }
1714

1815
#region EnvironmentBase
1916

src/shared/Core/Interop/Windows/WindowsEnvironment.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ namespace GitCredentialManager.Interop.Windows
1010
public class WindowsEnvironment : EnvironmentBase
1111
{
1212
public WindowsEnvironment(IFileSystem fileSystem)
13-
: this(fileSystem, null) { }
13+
: base(fileSystem) { }
1414

1515
internal WindowsEnvironment(IFileSystem fileSystem, IReadOnlyDictionary<string, string> variables)
16-
: base(fileSystem)
17-
{
18-
Variables = variables ?? GetCurrentVariables();
19-
}
16+
: base(fileSystem, variables) { }
2017

2118
#region EnvironmentBase
2219

@@ -48,7 +45,7 @@ public override void AddDirectoryToPath(string directoryPath, EnvironmentVariabl
4845
Environment.SetEnvironmentVariable("PATH", newValue, target);
4946

5047
// Update the cached PATH variable to the latest value (as well as all other variables)
51-
Variables = GetCurrentVariables();
48+
Refresh();
5249
}
5350

5451
public override void RemoveDirectoryFromPath(string directoryPath, EnvironmentVariableTarget target)
@@ -66,7 +63,7 @@ public override void RemoveDirectoryFromPath(string directoryPath, EnvironmentVa
6663
Environment.SetEnvironmentVariable("PATH", newValue, target);
6764

6865
// Update the cached PATH variable to the latest value (as well as all other variables)
69-
Variables = GetCurrentVariables();
66+
Refresh();
7067
}
7168
}
7269

src/shared/TestInfrastructure/Objects/TestEnvironment.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.ObjectModel;
4-
using System.Diagnostics;
54
using System.Linq;
65

76
namespace GitCredentialManager.Tests.Objects
@@ -106,6 +105,11 @@ public void SetEnvironmentVariable(string variable, string value,
106105
Variables.Add(variable, value);
107106
}
108107

108+
public void Refresh()
109+
{
110+
// Nothing to do!
111+
}
112+
109113
#endregion
110114
}
111115
}

0 commit comments

Comments
 (0)