Skip to content

Commit 3e99d7f

Browse files
authored
refactor: improve and consolidate interfaces (#906)
Make the following changes for the interfaces: 1. Enable [Nullable reference types](https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references) 2. Improve and cleanup XML documentation and sort properties and methods in interfaces alphabetically 3. Consolidate the naming in the factory methods to always use just `New`; so instead of `new DirectoryInfo(path)` you should use `IFileSystem.DirectoryInfo.New(path)`. The previous existing factory methods were kept and marked as `Obsolete`. 4. Added a `Wrap` method to the factory interfaces that can be used instead of a direct cast (which is not possible for interfaces) to create a wrapped interface from a "System.IO" type. 5. Removed the `WriteAllLinesAsync` with array overload, as it is not defined in `System.IO` and overload resolution will automatically use the `IEnumerable` overload. 6. Added missing methods: `ResolveLinkTarget` and `CreateAsSymbolicLink` 7. Change the return type for Stream-Methods to a custom defined `FileSystemStream`. *Note: This should fix the issues from #793 , right?* Added the following extensibility measures: - Add `IFileSystem FileSystem` property to more classes to enable implementing extension methods. This was simplified by adding a base interface `IFileSystemExtensionPoint` - Add a `IFileSystemExtensibility Extensibility` property on `IFileSystemInfo` and `IFileSystemStream` which grants access to the underlying wrapped instance or to a metadata dictionary to store additional information together with the instance.
1 parent 8a24fd2 commit 3e99d7f

File tree

87 files changed

+2098
-796
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+2098
-796
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1212
<PackageReadmeFile>README.md</PackageReadmeFile>
1313
<DefineConstants Condition="'$(TargetFramework)' != 'net461'">$(DefineConstants);FEATURE_FILE_SYSTEM_ACL_EXTENSIONS</DefineConstants>
14-
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0' OR '$(TargetFramework)' == 'netcoreapp3.1' OR '$(TargetFramework)' == 'netstandard2.1'">$(DefineConstants);FEATURE_ASYNC_FILE;FEATURE_ENUMERATION_OPTIONS;FEATURE_ADVANCED_PATH_OPERATIONS;FEATURE_PATH_JOIN_WITH_SPAN</DefineConstants>
14+
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0' OR '$(TargetFramework)' == 'netcoreapp3.1' OR '$(TargetFramework)' == 'netstandard2.1'">$(DefineConstants);FEATURE_ASYNC_FILE;FEATURE_ENUMERATION_OPTIONS;FEATURE_ADVANCED_PATH_OPERATIONS;FEATURE_PATH_JOIN_WITH_SPAN;FEATURE_SPAN</DefineConstants>
1515
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'net5.0'">$(DefineConstants);FEATURE_FILE_MOVE_WITH_OVERWRITE;FEATURE_SUPPORTED_OS_ATTRIBUTE;FEATURE_FILE_SYSTEM_WATCHER_FILTERS;FEATURE_ENDS_IN_DIRECTORY_SEPARATOR;FEATURE_PATH_JOIN_WITH_PARAMS;FEATURE_PATH_JOIN_WITH_FOUR_PATHS</DefineConstants>
16-
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0'">$(DefineConstants);FEATURE_FILE_SYSTEM_INFO_LINK_TARGET;FEATURE_CREATE_SYMBOLIC_LINK</DefineConstants>
16+
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0'">$(DefineConstants);FEATURE_FILE_SYSTEM_INFO_LINK_TARGET;FEATURE_CREATE_SYMBOLIC_LINK;FEATURE_FILESTREAM_OPTIONS</DefineConstants>
1717
</PropertyGroup>
1818
<ItemGroup>
1919
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.119">

System.IO.Abstractions.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Global
7070
EndGlobalSection
7171
GlobalSection(NestedProjects) = preSolution
7272
{20B02738-952A-40F5-9C10-E2F83013E9FC} = {BCEC61BD-4941-41EC-975A-ACEFC7AC1780}
73+
{015B3812-E01D-479C-895D-BDDF16E798CA} = {BCEC61BD-4941-41EC-975A-ACEFC7AC1780}
7374
{7105D748-1253-409F-A624-4879412EF3C2} = {BCEC61BD-4941-41EC-975A-ACEFC7AC1780}
7475
EndGlobalSection
7576
GlobalSection(ExtensibilityGlobals) = postSolution

benchmarks/System.IO.Abstractions.Benchmarks/Support/DirectorySupport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public string CreateRandomDirectory()
4343
private void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs = true, bool overwrite = true)
4444
{
4545
// Get the subdirectories for the specified directory.
46-
var dir = _fileSystem.DirectoryInfo.FromDirectoryName(sourceDirName);
46+
var dir = _fileSystem.DirectoryInfo.New(sourceDirName);
4747
if (!dir.Exists)
4848
{
4949
throw new DirectoryNotFoundException(

src/System.IO.Abstractions.TestingHelpers/MockDirectory.cs

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.ComponentModel;
23
using System.Globalization;
34
using System.Linq;
45
using System.Runtime.Versioning;
@@ -21,15 +22,17 @@ public class MockDirectory : DirectoryBase
2122

2223
/// <inheritdoc />
2324
public MockDirectory(IMockFileDataAccessor mockFileDataAccessor, FileBase fileBase, string currentDirectory) :
24-
this(mockFileDataAccessor, currentDirectory)
25+
this(mockFileDataAccessor, currentDirectory)
2526
{
2627
}
2728

2829
/// <inheritdoc />
29-
public MockDirectory(IMockFileDataAccessor mockFileDataAccessor, string currentDirectory) : base(mockFileDataAccessor?.FileSystem)
30+
public MockDirectory(IMockFileDataAccessor mockFileDataAccessor, string currentDirectory) : base(
31+
mockFileDataAccessor?.FileSystem)
3032
{
3133
this.currentDirectory = currentDirectory;
32-
this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor));
34+
this.mockFileDataAccessor =
35+
mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor));
3336
}
3437

3538

@@ -55,7 +58,8 @@ private IDirectoryInfo CreateDirectoryInternal(string path, DirectorySecurity di
5558

5659
if (path.Length == 0)
5760
{
58-
throw new ArgumentException(StringResources.Manager.GetString("PATH_CANNOT_BE_THE_EMPTY_STRING_OR_ALL_WHITESPACE"), "path");
61+
throw new ArgumentException(
62+
StringResources.Manager.GetString("PATH_CANNOT_BE_THE_EMPTY_STRING_OR_ALL_WHITESPACE"), "path");
5963
}
6064

6165
if (mockFileDataAccessor.PathVerifier.HasIllegalCharacters(path, true))
@@ -136,7 +140,9 @@ public override void Delete(string path, bool recursive)
136140

137141
if (!recursive && affectedPaths.Count > 1)
138142
{
139-
throw new IOException("The directory specified by " + path + " is read-only, or recursive is false and " + path + " is not an empty directory.");
143+
throw new IOException("The directory specified by " + path +
144+
" is read-only, or recursive is false and " + path +
145+
" is not an empty directory.");
140146
}
141147

142148
foreach (var affectedPath in affectedPaths)
@@ -231,7 +237,8 @@ public override string[] GetDirectories(string path, string searchPattern, Searc
231237

232238
#if FEATURE_ENUMERATION_OPTIONS
233239
/// <inheritdoc />
234-
public override string[] GetDirectories(string path, string searchPattern, EnumerationOptions enumerationOptions)
240+
public override string[] GetDirectories(string path, string searchPattern,
241+
EnumerationOptions enumerationOptions)
235242
{
236243
return GetDirectories(path, "*", EnumerationOptionsToSearchOption(enumerationOptions));
237244
}
@@ -331,10 +338,12 @@ private string[] GetFilesInternal(
331338
.Replace(@"\?", isUnix ? @"[^<>:""/|?*]?" : @"[^<>:""/\\|?*]?");
332339

333340
var extension = Path.GetExtension(searchPattern);
334-
bool hasExtensionLengthOfThree = extension != null && extension.Length == 4 && !extension.Contains("*") && !extension.Contains("?");
341+
bool hasExtensionLengthOfThree = extension != null && extension.Length == 4 &&
342+
!extension.Contains("*") && !extension.Contains("?");
335343
if (hasExtensionLengthOfThree)
336344
{
337-
var fileNamePatternSpecial = string.Format(CultureInfo.InvariantCulture, "{0}[^.]", fileNamePattern);
345+
var fileNamePatternSpecial =
346+
string.Format(CultureInfo.InvariantCulture, "{0}[^.]", fileNamePattern);
338347
pathPatternSpecial = string.Format(
339348
CultureInfo.InvariantCulture,
340349
isUnix ? @"(?i:^{0}{1}{2}(?:/?)$)" : @"(?i:^{0}{1}{2}(?:\\?)$)",
@@ -358,9 +367,10 @@ private string[] GetFilesInternal(
358367
}
359368

360369
return files.Where(p =>
361-
!searchEndInStarDot ?
362-
(Regex.IsMatch(p, pathPattern) || (pathPatternSpecial != null && Regex.IsMatch(p, pathPatternSpecial)))
363-
: (Regex.IsMatch(p, pathPatternNoExtension) || Regex.IsMatch(p, pathPatternEndsInDot))
370+
!searchEndInStarDot
371+
? (Regex.IsMatch(p, pathPattern) ||
372+
(pathPatternSpecial != null && Regex.IsMatch(p, pathPatternSpecial)))
373+
: (Regex.IsMatch(p, pathPatternNoExtension) || Regex.IsMatch(p, pathPatternEndsInDot))
364374
)
365375
.ToArray();
366376
}
@@ -389,6 +399,15 @@ public override string[] GetFileSystemEntries(string path, string searchPattern,
389399
return dirs.Union(files).ToArray();
390400
}
391401

402+
#if FEATURE_ENUMERATION_OPTIONS
403+
/// <inheritdoc />
404+
public override string[] GetFileSystemEntries(string path, string searchPattern,
405+
EnumerationOptions enumerationOptions)
406+
{
407+
return GetFileSystemEntries(path, "*", EnumerationOptionsToSearchOption(enumerationOptions));
408+
}
409+
#endif
410+
392411
/// <inheritdoc />
393412
public override DateTime GetLastAccessTime(string path)
394413
{
@@ -434,7 +453,8 @@ public override IDirectoryInfo GetParent(string path)
434453

435454
if (path.Length == 0)
436455
{
437-
throw new ArgumentException(StringResources.Manager.GetString("PATH_CANNOT_BE_THE_EMPTY_STRING_OR_ALL_WHITESPACE"), "path");
456+
throw new ArgumentException(
457+
StringResources.Manager.GetString("PATH_CANNOT_BE_THE_EMPTY_STRING_OR_ALL_WHITESPACE"), "path");
438458
}
439459

440460
if (mockFileDataAccessor.PathVerifier.HasIllegalCharacters(path, false))
@@ -469,9 +489,11 @@ public override IDirectoryInfo GetParent(string path)
469489
{
470490
absolutePath = absolutePath.TrimSlashes();
471491

472-
if (absolutePath.Length > 1 && absolutePath.LastIndexOf(mockFileDataAccessor.Path.DirectorySeparatorChar) == 0)
492+
if (absolutePath.Length > 1 &&
493+
absolutePath.LastIndexOf(mockFileDataAccessor.Path.DirectorySeparatorChar) == 0)
473494
{
474-
return new MockDirectoryInfo(mockFileDataAccessor, mockFileDataAccessor.Path.DirectorySeparatorChar.ToString());
495+
return new MockDirectoryInfo(mockFileDataAccessor,
496+
mockFileDataAccessor.Path.DirectorySeparatorChar.ToString());
475497
}
476498
}
477499

@@ -505,7 +527,8 @@ public override void Move(string sourceDirName, string destDirName)
505527

506528
if (!mockFileDataAccessor.StringOperations.Equals(sourceRoot, destinationRoot))
507529
{
508-
throw new IOException("Source and destination path must have identical roots. Move will not work across volumes.");
530+
throw new IOException(
531+
"Source and destination path must have identical roots. Move will not work across volumes.");
509532
}
510533

511534
if (!mockFileDataAccessor.Directory.Exists(fullSourcePath))
@@ -520,12 +543,22 @@ public override void Move(string sourceDirName, string destDirName)
520543

521544
if (mockFileDataAccessor.Directory.Exists(fullDestPath) || mockFileDataAccessor.File.Exists(fullDestPath))
522545
{
523-
throw new IOException($"Cannot create '{fullDestPath}' because a file or directory with the same name already exists.");
546+
throw new IOException(
547+
$"Cannot create '{fullDestPath}' because a file or directory with the same name already exists.");
524548
}
525549

526550
mockFileDataAccessor.MoveDirectory(fullSourcePath, fullDestPath);
527551
}
528552

553+
#if FEATURE_CREATE_SYMBOLIC_LINK
554+
/// <inheritdoc />
555+
public override IFileSystemInfo ResolveLinkTarget(string linkPath, bool returnFinalTarget)
556+
{
557+
throw new NotImplementedException();
558+
}
559+
560+
#endif
561+
529562
/// <inheritdoc />
530563
[SupportedOSPlatform("windows")]
531564
public override void SetAccessControl(string path, DirectorySecurity directorySecurity)

src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfo.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ public MockDirectoryInfo(IMockFileDataAccessor mockFileDataAccessor, string dire
3838
Refresh();
3939
}
4040

41+
#if FEATURE_CREATE_SYMBOLIC_LINK
42+
/// <inheritdoc />
43+
public override void CreateAsSymbolicLink(string pathToTarget)
44+
{
45+
throw new NotImplementedException();
46+
}
47+
#endif
48+
4149
/// <inheritdoc />
4250
public override void Delete()
4351
{
@@ -52,6 +60,14 @@ public override void Refresh()
5260
cachedMockFileData = mockFileData.Clone();
5361
}
5462

63+
#if FEATURE_CREATE_SYMBOLIC_LINK
64+
/// <inheritdoc />
65+
public override IFileSystemInfo ResolveLinkTarget(bool returnFinalTarget)
66+
{
67+
throw new NotImplementedException();
68+
}
69+
#endif
70+
5571
/// <inheritdoc />
5672
public override FileAttributes Attributes
5773
{
@@ -337,7 +353,7 @@ public override IFileInfo[] GetFiles(string searchPattern, EnumerationOptions en
337353
IFileInfo[] ConvertStringsToFiles(IEnumerable<string> paths)
338354
{
339355
return paths
340-
.Select(mockFileDataAccessor.FileInfo.FromFileName)
356+
.Select(mockFileDataAccessor.FileInfo.New)
341357
.ToArray();
342358
}
343359

src/System.IO.Abstractions.TestingHelpers/MockDirectoryInfoFactory.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,27 @@ public MockDirectoryInfoFactory(IMockFileDataAccessor mockFileSystem)
1313
}
1414

1515
/// <inheritdoc />
16+
public IFileSystem FileSystem
17+
=> mockFileSystem;
18+
19+
20+
/// <inheritdoc />
21+
[Obsolete("Use `IDirectoryInfoFactory.New(string)` instead")]
1622
public IDirectoryInfo FromDirectoryName(string directoryName)
1723
{
18-
return new MockDirectoryInfo(mockFileSystem, directoryName);
24+
return New(directoryName);
25+
}
26+
27+
/// <inheritdoc />
28+
public IDirectoryInfo New(string path)
29+
{
30+
return new MockDirectoryInfo(mockFileSystem, path);
31+
}
32+
33+
/// <inheritdoc />
34+
public IDirectoryInfo Wrap(DirectoryInfo directoryInfo)
35+
{
36+
return new MockDirectoryInfo(mockFileSystem, directoryInfo.Name);
1937
}
2038
}
2139
}

src/System.IO.Abstractions.TestingHelpers/MockDriveInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public override IDirectoryInfo RootDirectory
5656
{
5757
get
5858
{
59-
return mockFileDataAccessor.DirectoryInfo.FromDirectoryName(Name);
59+
return mockFileDataAccessor.DirectoryInfo.New(Name);
6060
}
6161
}
6262

src/System.IO.Abstractions.TestingHelpers/MockDriveInfoFactory.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public MockDriveInfoFactory(IMockFileDataAccessor mockFileSystem)
1414
this.mockFileSystem = mockFileSystem ?? throw new ArgumentNullException(nameof(mockFileSystem));
1515
}
1616

17+
/// <inheritdoc />
18+
public IFileSystem FileSystem
19+
=> mockFileSystem;
20+
1721
/// <inheritdoc />
1822
public IDriveInfo[] GetDrives()
1923
{
@@ -42,13 +46,26 @@ public IDriveInfo[] GetDrives()
4246
}
4347

4448
/// <inheritdoc />
45-
public IDriveInfo FromDriveName(string driveName)
49+
public IDriveInfo New(string driveName)
4650
{
4751
var drive = mockFileSystem.Path.GetPathRoot(driveName);
4852

4953
return new MockDriveInfo(mockFileSystem, drive);
5054
}
5155

56+
/// <inheritdoc />
57+
public IDriveInfo Wrap(DriveInfo driveInfo)
58+
{
59+
return New(driveInfo.Name);
60+
}
61+
62+
/// <inheritdoc />
63+
[Obsolete("Use `IDirectoryInfoFactory.New(string)` instead")]
64+
public IDriveInfo FromDriveName(string driveName)
65+
{
66+
return New(driveName);
67+
}
68+
5269
private string NormalizeDriveName(string driveName)
5370
{
5471
if (driveName.Length == 3 && mockFileSystem.StringOperations.EndsWith(driveName, @":\"))

src/System.IO.Abstractions.TestingHelpers/MockFile.Async.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,6 @@ public override Task WriteAllLinesAsync(string path, IEnumerable<string> content
8585
return Task.CompletedTask;
8686
}
8787

88-
/// <inheritdoc />
89-
public override Task WriteAllLinesAsync(string path, string[] contents, CancellationToken cancellationToken) =>
90-
WriteAllLinesAsync(path, contents, MockFileData.DefaultEncoding, cancellationToken);
91-
92-
/// <inheritdoc />
93-
public override Task WriteAllLinesAsync(string path, string[] contents, Encoding encoding, CancellationToken cancellationToken)
94-
{
95-
cancellationToken.ThrowIfCancellationRequested();
96-
WriteAllLines(path, contents, encoding);
97-
return Task.CompletedTask;
98-
}
99-
10088
/// <inheritdoc />
10189
public override Task WriteAllTextAsync(string path, string contents, CancellationToken cancellationToken) =>
10290
WriteAllTextAsync(path, contents, MockFileData.DefaultEncoding, cancellationToken);

0 commit comments

Comments
 (0)