diff --git a/src/GitVersion.LibGit2Sharp/Git/Branch.cs b/src/GitVersion.LibGit2Sharp/Git/Branch.cs index bfa286cad8..3091fde193 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Branch.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Branch.cs @@ -10,18 +10,18 @@ internal sealed class Branch : IBranch private readonly LibGit2Sharp.Branch innerBranch; - internal Branch(LibGit2Sharp.Branch branch, LibGit2Sharp.Diff diff, GitRepository repo) + internal Branch(LibGit2Sharp.Branch branch, LibGit2Sharp.Diff diff, GitRepositoryCache repositoryCache) { diff.NotNull(); - repo.NotNull(); + repositoryCache.NotNull(); this.innerBranch = branch.NotNull(); Name = new(branch.CanonicalName); var commit = this.innerBranch.Tip; - Tip = commit is null ? null : repo.GetOrCreate(commit, diff); + Tip = commit is null ? null : repositoryCache.GetOrWrap(commit, diff); var commits = this.innerBranch.Commits; - Commits = new CommitCollection(commits, diff, repo); + Commits = new CommitCollection(commits, diff, repositoryCache); } public ReferenceName Name { get; } diff --git a/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs b/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs index b8b1df839d..5154ee5191 100644 --- a/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs @@ -8,18 +8,17 @@ internal sealed class BranchCollection : IBranchCollection private readonly LibGit2Sharp.BranchCollection innerCollection; private readonly Lazy> branches; private readonly Diff diff; - private readonly GitRepository repo; + private readonly GitRepositoryCache repositoryCache; - internal BranchCollection(LibGit2Sharp.BranchCollection collection, Diff diff, GitRepository repo) + internal BranchCollection(LibGit2Sharp.BranchCollection collection, Diff diff, GitRepositoryCache repositoryCache) { this.innerCollection = collection.NotNull(); - this.branches = new Lazy>(() => [.. this.innerCollection.Select(branch => repo.GetOrCreate(branch, diff))]); + this.branches = new Lazy>(() => [.. this.innerCollection.Select(branch => repositoryCache.GetOrWrap(branch, diff))]); this.diff = diff.NotNull(); - this.repo = repo.NotNull(); + this.repositoryCache = repositoryCache.NotNull(); } - public IEnumerator GetEnumerator() - => this.branches.Value.GetEnumerator(); + public IEnumerator GetEnumerator() => this.branches.Value.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -29,7 +28,7 @@ public IBranch? this[string name] { name = name.NotNull(); var branch = this.innerCollection[name]; - return branch is null ? null : this.repo.GetOrCreate(branch, this.diff); + return branch is null ? null : this.repositoryCache.GetOrWrap(branch, this.diff); } } diff --git a/src/GitVersion.LibGit2Sharp/Git/Commit.cs b/src/GitVersion.LibGit2Sharp/Git/Commit.cs index 1539de1bf0..f7c59ed5eb 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Commit.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Commit.cs @@ -14,12 +14,12 @@ internal sealed class Commit : ICommit private readonly LibGit2Sharp.Commit innerCommit; private readonly LibGit2Sharp.Diff repoDiff; - internal Commit(LibGit2Sharp.Commit innerCommit, LibGit2Sharp.Diff repoDiff, GitRepository repo) + internal Commit(LibGit2Sharp.Commit innerCommit, LibGit2Sharp.Diff repoDiff, GitRepositoryCache repositoryCache) { repoDiff.NotNull(); - repo.NotNull(); + repositoryCache.NotNull(); this.innerCommit = innerCommit.NotNull(); - this.parentsLazy = new(() => innerCommit.Parents.Select(parent => repo.GetOrCreate(parent, repoDiff)).ToList()); + this.parentsLazy = new(() => [.. innerCommit.Parents.Select(parent => repositoryCache.GetOrWrap(parent, repoDiff))]); Id = new ObjectId(innerCommit.Id); Sha = innerCommit.Sha; When = innerCommit.Committer.When; diff --git a/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs b/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs index 05d9940f94..2ab085e8c7 100644 --- a/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs @@ -8,18 +8,17 @@ internal sealed class CommitCollection : ICommitCollection private readonly ICommitLog innerCollection; private readonly Lazy> commits; private readonly Diff diff; - private readonly GitRepository repo; + private readonly GitRepositoryCache repositoryCache; - internal CommitCollection(ICommitLog collection, Diff diff, GitRepository repo) + internal CommitCollection(ICommitLog collection, Diff diff, GitRepositoryCache repositoryCache) { this.innerCollection = collection.NotNull(); - this.commits = new Lazy>(() => [.. this.innerCollection.Select(commit => repo.GetOrCreate(commit, diff))]); + this.commits = new Lazy>(() => [.. this.innerCollection.Select(commit => repositoryCache.GetOrWrap(commit, diff))]); this.diff = diff.NotNull(); - this.repo = repo.NotNull(); + this.repositoryCache = repositoryCache.NotNull(); } - public IEnumerator GetEnumerator() - => this.commits.Value.GetEnumerator(); + public IEnumerator GetEnumerator() => this.commits.Value.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -38,7 +37,7 @@ public IEnumerable QueryBy(CommitFilter commitFilter) SortBy = (LibGit2Sharp.CommitSortStrategies)commitFilter.SortBy }; var commitLog = ((IQueryableCommitLog)this.innerCollection).QueryBy(filter); - return new CommitCollection(commitLog, this.diff, this.repo); + return new CommitCollection(commitLog, this.diff, this.repositoryCache); static object? GetReacheableFrom(object? item) => item switch diff --git a/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs b/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs index c3c659dbdc..a562bec0bc 100644 --- a/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs +++ b/src/GitVersion.LibGit2Sharp/Git/GitRepository.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using GitVersion.Extensions; using GitVersion.Helpers; using LibGit2Sharp; @@ -8,10 +7,7 @@ namespace GitVersion.Git; internal sealed partial class GitRepository { private Lazy? repositoryLazy; - - private readonly ConcurrentDictionary cachedBranches = new(); - private readonly ConcurrentDictionary cachedCommits = new(); - private readonly ConcurrentDictionary cachedTags = new(); + private readonly GitRepositoryCache repositoryCache = new(); private IRepository RepositoryInstance { @@ -25,13 +21,21 @@ private IRepository RepositoryInstance public string WorkingDirectory => RepositoryInstance.Info.WorkingDirectory; public bool IsHeadDetached => RepositoryInstance.Info.IsHeadDetached; public bool IsShallow => RepositoryInstance.Info.IsShallow; - public IBranch Head => GetOrCreate(RepositoryInstance.Head, RepositoryInstance.Diff); + public IBranch Head => this.repositoryCache.GetOrWrap(RepositoryInstance.Head, RepositoryInstance.Diff); + + private ITagCollection? tags; + public ITagCollection Tags => this.tags ??= new TagCollection(RepositoryInstance.Tags, RepositoryInstance.Diff, this.repositoryCache); + + public IBranchCollection Branches => new BranchCollection(RepositoryInstance.Branches, RepositoryInstance.Diff, this.repositoryCache); + + private ICommitCollection? commits; + public ICommitCollection Commits => this.commits ??= new CommitCollection(RepositoryInstance.Commits, RepositoryInstance.Diff, this.repositoryCache); - public ITagCollection Tags => new TagCollection(RepositoryInstance.Tags, RepositoryInstance.Diff, this); - public IBranchCollection Branches => new BranchCollection(RepositoryInstance.Branches, RepositoryInstance.Diff, this); - public ICommitCollection Commits => new CommitCollection(RepositoryInstance.Commits, RepositoryInstance.Diff, this); - public IRemoteCollection Remotes => new RemoteCollection(RepositoryInstance.Network.Remotes); - public IReferenceCollection References => new ReferenceCollection(RepositoryInstance.Refs); + private IRemoteCollection? remotes; + public IRemoteCollection Remotes => this.remotes ??= new RemoteCollection(RepositoryInstance.Network.Remotes, this.repositoryCache); + + private IReferenceCollection? references; + public IReferenceCollection References => this.references ??= new ReferenceCollection(RepositoryInstance.Refs, this.repositoryCache); public void DiscoverRepository(string? gitDirectory) { @@ -53,7 +57,7 @@ public void DiscoverRepository(string? gitDirectory) var first = (Commit)commit; var second = (Commit)otherCommit; var mergeBase = RepositoryInstance.ObjectDatabase.FindMergeBase(first, second); - return mergeBase == null ? null : GetOrCreate(mergeBase, RepositoryInstance.Diff); + return mergeBase == null ? null : this.repositoryCache.GetOrWrap(mergeBase, RepositoryInstance.Diff); }); } @@ -63,26 +67,6 @@ public int UncommittedChangesCount() return retryAction.Execute(GetUncommittedChangesCountInternal); } - public Branch GetOrCreate(LibGit2Sharp.Branch innerBranch, Diff repoDiff) - { - if (innerBranch.Tip is null) - { - return new Branch(innerBranch, repoDiff, this); - } - - var cacheKey = $"{innerBranch.CanonicalName}|{innerBranch.Tip.Sha}|{innerBranch.RemoteName}"; - return cachedBranches.GetOrAdd(cacheKey, new Branch(innerBranch, repoDiff, this)); - } - - public Commit GetOrCreate(LibGit2Sharp.Commit innerCommit, Diff repoDiff) => - cachedCommits.GetOrAdd(innerCommit.Sha, new Commit(innerCommit, repoDiff, this)); - - public Tag GetOrCreate(LibGit2Sharp.Tag innerTag, Diff repoDiff) - { - var cacheKey = $"{innerTag.CanonicalName}|{innerTag.Target.Sha}"; - return cachedTags.GetOrAdd(cacheKey, new Tag(innerTag, repoDiff, this)); - } - public void Dispose() { if (this.repositoryLazy is { IsValueCreated: true }) RepositoryInstance.Dispose(); diff --git a/src/GitVersion.LibGit2Sharp/Git/GitRepositoryCache.cs b/src/GitVersion.LibGit2Sharp/Git/GitRepositoryCache.cs new file mode 100644 index 0000000000..8d8eba006b --- /dev/null +++ b/src/GitVersion.LibGit2Sharp/Git/GitRepositoryCache.cs @@ -0,0 +1,37 @@ +using System.Collections.Concurrent; +using LibGit2Sharp; + +namespace GitVersion.Git; + +internal class GitRepositoryCache +{ + private readonly ConcurrentDictionary cachedBranches = new(); + private readonly ConcurrentDictionary cachedCommits = new(); + private readonly ConcurrentDictionary cachedTags = new(); + private readonly ConcurrentDictionary cachedRemotes = new(); + private readonly ConcurrentDictionary cachedReferences = new(); + private readonly ConcurrentDictionary cachedRefSpecs = new(); + + public Branch GetOrWrap(LibGit2Sharp.Branch innerBranch, Diff repoDiff) + { + var cacheKey = innerBranch.Tip is null + ? $"{innerBranch.RemoteName}/{innerBranch.CanonicalName}" + : $"{innerBranch.RemoteName}/{innerBranch.CanonicalName}@{innerBranch.Tip.Sha}"; + return cachedBranches.GetOrAdd(cacheKey, _ => new Branch(innerBranch, repoDiff, this)); + } + + public Commit GetOrWrap(LibGit2Sharp.Commit innerCommit, Diff repoDiff) + => cachedCommits.GetOrAdd(innerCommit.Sha, _ => new Commit(innerCommit, repoDiff, this)); + + public Tag GetOrWrap(LibGit2Sharp.Tag innerTag, Diff repoDiff) + => cachedTags.GetOrAdd(innerTag.CanonicalName, _ => new Tag(innerTag, repoDiff, this)); + + public Remote GetOrWrap(LibGit2Sharp.Remote innerRemote) + => cachedRemotes.GetOrAdd(innerRemote.Name, _ => new Remote(innerRemote, this)); + + public Reference GetOrWrap(LibGit2Sharp.Reference innerReference) + => cachedReferences.GetOrAdd(innerReference.CanonicalName, _ => new Reference(innerReference)); + + public RefSpec GetOrWrap(LibGit2Sharp.RefSpec innerRefSpec) + => cachedRefSpecs.GetOrAdd(innerRefSpec.Specification, _ => new RefSpec(innerRefSpec)); +} diff --git a/src/GitVersion.LibGit2Sharp/Git/RefSpecCollection.cs b/src/GitVersion.LibGit2Sharp/Git/RefSpecCollection.cs index ed67a52333..3ae987a10f 100644 --- a/src/GitVersion.LibGit2Sharp/Git/RefSpecCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/RefSpecCollection.cs @@ -6,10 +6,10 @@ internal sealed class RefSpecCollection : IRefSpecCollection { private readonly Lazy> refSpecs; - internal RefSpecCollection(LibGit2Sharp.RefSpecCollection collection) + internal RefSpecCollection(LibGit2Sharp.RefSpecCollection innerCollection, GitRepositoryCache repositoryCache) { - collection = collection.NotNull(); - this.refSpecs = new Lazy>(() => [.. collection.Select(tag => new RefSpec(tag))]); + innerCollection = innerCollection.NotNull(); + this.refSpecs = new Lazy>(() => [.. innerCollection.Select(repositoryCache.GetOrWrap)]); } public IEnumerator GetEnumerator() => this.refSpecs.Value.GetEnumerator(); diff --git a/src/GitVersion.LibGit2Sharp/Git/ReferenceCollection.cs b/src/GitVersion.LibGit2Sharp/Git/ReferenceCollection.cs index 2aa74a2140..5e0b795cd3 100644 --- a/src/GitVersion.LibGit2Sharp/Git/ReferenceCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/ReferenceCollection.cs @@ -5,23 +5,17 @@ namespace GitVersion.Git; internal sealed class ReferenceCollection : IReferenceCollection { private readonly LibGit2Sharp.ReferenceCollection innerCollection; - private IReadOnlyCollection? references; + private readonly GitRepositoryCache repositoryCache; + private Lazy> references = null!; - internal ReferenceCollection(LibGit2Sharp.ReferenceCollection collection) => this.innerCollection = collection.NotNull(); - - public IEnumerator GetEnumerator() + internal ReferenceCollection(LibGit2Sharp.ReferenceCollection collection, GitRepositoryCache repositoryCache) { - this.references ??= [.. this.innerCollection.Select(reference => new Reference(reference))]; - return this.references.GetEnumerator(); + this.innerCollection = collection.NotNull(); + this.repositoryCache = repositoryCache.NotNull(); + InitializeReferencesLazy(); } - public void Add(string name, string canonicalRefNameOrObject, bool allowOverwrite = false) => this.innerCollection.Add(name, canonicalRefNameOrObject, allowOverwrite); - - public void UpdateTarget(IReference directRef, IObjectId targetId) - { - RepositoryExtensions.RunSafe(() => this.innerCollection.UpdateTarget((Reference)directRef, (ObjectId)targetId)); - this.references = null; - } + public IEnumerator GetEnumerator() => this.references.Value.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -30,7 +24,7 @@ public IReference? this[string name] get { var reference = this.innerCollection[name]; - return reference is null ? null : new Reference(reference); + return reference is null ? null : this.repositoryCache.GetOrWrap(reference); } } @@ -38,5 +32,23 @@ public IReference? this[string name] public IReference? Head => this["HEAD"]; - public IEnumerable FromGlob(string prefix) => this.innerCollection.FromGlob(prefix).Select(reference => new Reference(reference)); + public IEnumerable FromGlob(string prefix) + => this.innerCollection.FromGlob(prefix).Select(reference => this.repositoryCache.GetOrWrap(reference)); + + public void Add(string name, string canonicalRefNameOrObject, bool allowOverwrite = false) => + RepositoryExtensions.RunSafe(() => + { + this.innerCollection.Add(name, canonicalRefNameOrObject, allowOverwrite); + InitializeReferencesLazy(); + }); + + public void UpdateTarget(IReference directRef, IObjectId targetId) => + RepositoryExtensions.RunSafe(() => + { + this.innerCollection.UpdateTarget((Reference)directRef, (ObjectId)targetId); + InitializeReferencesLazy(); + }); + + private void InitializeReferencesLazy() + => this.references = new Lazy>(() => [.. this.innerCollection.Select(repositoryCache.GetOrWrap)]); } diff --git a/src/GitVersion.LibGit2Sharp/Git/Remote.cs b/src/GitVersion.LibGit2Sharp/Git/Remote.cs index 9b409824e7..e6f0099a7c 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Remote.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Remote.cs @@ -9,22 +9,27 @@ internal sealed class Remote : IRemote private static readonly LambdaKeyComparer comparerHelper = new(x => x.Name); private readonly LibGit2Sharp.Remote innerRemote; + private readonly GitRepositoryCache repositoryCache; - internal Remote(LibGit2Sharp.Remote remote) => this.innerRemote = remote.NotNull(); + internal Remote(LibGit2Sharp.Remote remote, GitRepositoryCache repositoryCache) + { + this.innerRemote = remote.NotNull(); + this.repositoryCache = repositoryCache.NotNull(); + } public int CompareTo(IRemote? other) => comparerHelper.Compare(this, other); public bool Equals(IRemote? other) => equalityHelper.Equals(this, other); public string Name => this.innerRemote.Name; public string Url => this.innerRemote.Url; - public IEnumerable RefSpecs + private IEnumerable RefSpecs { get { var refSpecs = this.innerRemote.RefSpecs; return refSpecs is null ? [] - : new RefSpecCollection((LibGit2Sharp.RefSpecCollection)refSpecs); + : new RefSpecCollection((LibGit2Sharp.RefSpecCollection)refSpecs, this.repositoryCache); } } diff --git a/src/GitVersion.LibGit2Sharp/Git/RemoteCollection.cs b/src/GitVersion.LibGit2Sharp/Git/RemoteCollection.cs index b328c56875..c63f135823 100644 --- a/src/GitVersion.LibGit2Sharp/Git/RemoteCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/RemoteCollection.cs @@ -5,16 +5,18 @@ namespace GitVersion.Git; internal sealed class RemoteCollection : IRemoteCollection { private readonly LibGit2Sharp.RemoteCollection innerCollection; - private IReadOnlyCollection? remotes; + private readonly GitRepositoryCache repositoryCache; + private Lazy> remotes = null!; - internal RemoteCollection(LibGit2Sharp.RemoteCollection collection) => this.innerCollection = collection.NotNull(); - - public IEnumerator GetEnumerator() + internal RemoteCollection(LibGit2Sharp.RemoteCollection collection, GitRepositoryCache repositoryCache) { - this.remotes ??= [.. this.innerCollection.Select(reference => new Remote(reference))]; - return this.remotes.GetEnumerator(); + this.innerCollection = collection.NotNull(); + this.repositoryCache = repositoryCache.NotNull(); + InitializeRemotesLazy(); } + public IEnumerator GetEnumerator() => this.remotes.Value.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public IRemote? this[string name] @@ -22,19 +24,24 @@ public IRemote? this[string name] get { var remote = this.innerCollection[name]; - return remote is null ? null : new Remote(remote); + return remote is null ? null : this.repositoryCache.GetOrWrap(remote); } } - public void Remove(string remoteName) - { - this.innerCollection.Remove(remoteName); - this.remotes = null; - } + public void Remove(string remoteName) => + RepositoryExtensions.RunSafe(() => + { + this.innerCollection.Remove(remoteName); + InitializeRemotesLazy(); + }); - public void Update(string remoteName, string refSpec) - { - this.innerCollection.Update(remoteName, r => r.FetchRefSpecs.Add(refSpec)); - this.remotes = null; - } + public void Update(string remoteName, string refSpec) => + RepositoryExtensions.RunSafe(() => + { + this.innerCollection.Update(remoteName, r => r.FetchRefSpecs.Add(refSpec)); + InitializeRemotesLazy(); + }); + + private void InitializeRemotesLazy() + => this.remotes = new Lazy>(() => [.. this.innerCollection.Select(repositoryCache.GetOrWrap)]); } diff --git a/src/GitVersion.LibGit2Sharp/Git/Tag.cs b/src/GitVersion.LibGit2Sharp/Git/Tag.cs index 89fb6d777a..47743c6eac 100644 --- a/src/GitVersion.LibGit2Sharp/Git/Tag.cs +++ b/src/GitVersion.LibGit2Sharp/Git/Tag.cs @@ -11,14 +11,14 @@ internal sealed class Tag : ITag private readonly LibGit2Sharp.Tag innerTag; private readonly Diff diff; private readonly Lazy commitLazy; - private readonly GitRepository repo; + private readonly GitRepositoryCache repositoryCache; - internal Tag(LibGit2Sharp.Tag tag, Diff diff, GitRepository repo) + internal Tag(LibGit2Sharp.Tag tag, Diff diff, GitRepositoryCache repositoryCache) { this.innerTag = tag.NotNull(); - this.commitLazy = new(PeeledTargetCommit); this.diff = diff.NotNull(); - this.repo = repo.NotNull(); + this.repositoryCache = repositoryCache.NotNull(); + this.commitLazy = new(PeeledTargetCommit); Name = new(this.innerTag.CanonicalName); } @@ -28,7 +28,7 @@ internal Tag(LibGit2Sharp.Tag tag, Diff diff, GitRepository repo) public string TargetSha => this.innerTag.Target.Sha; public ICommit Commit => this.commitLazy.Value.NotNull(); - private Commit? PeeledTargetCommit() + private ICommit? PeeledTargetCommit() { var target = this.innerTag.Target; @@ -37,7 +37,7 @@ internal Tag(LibGit2Sharp.Tag tag, Diff diff, GitRepository repo) target = annotation.Target; } - return target is LibGit2Sharp.Commit commit ? this.repo.GetOrCreate(commit, this.diff) : null; + return target is LibGit2Sharp.Commit commit ? this.repositoryCache.GetOrWrap(commit, this.diff) : null; } public override bool Equals(object? obj) => Equals(obj as ITag); diff --git a/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs b/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs index ec339e28b3..8ff39d4b4a 100644 --- a/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs +++ b/src/GitVersion.LibGit2Sharp/Git/TagCollection.cs @@ -6,16 +6,15 @@ internal sealed class TagCollection : ITagCollection { private readonly Lazy> tags; - internal TagCollection(LibGit2Sharp.TagCollection collection, LibGit2Sharp.Diff diff, GitRepository repo) + internal TagCollection(LibGit2Sharp.TagCollection collection, LibGit2Sharp.Diff diff, GitRepositoryCache repositoryCache) { collection.NotNull(); diff.NotNull(); - repo.NotNull(); - this.tags = new Lazy>(() => [.. collection.Select(tag => repo.GetOrCreate(tag, diff))]); + repositoryCache.NotNull(); + this.tags = new Lazy>(() => [.. collection.Select(tag => repositoryCache.GetOrWrap(tag, diff))]); } - public IEnumerator GetEnumerator() - => this.tags.Value.GetEnumerator(); + public IEnumerator GetEnumerator() => this.tags.Value.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }