Centralize ExcludeProperty filter application in ViewGenerator base class#26574
Conversation
| /// <summary> | ||
| /// Gets the current active association list for read-only access. | ||
| /// </summary> | ||
| protected IReadOnlyList<MshResolvedExpressionParameterAssociation> ActiveAssociationList => _activeAssociationList; |
There was a problem hiding this comment.
I'd prefer use exactly List type. We have no need to use generics and pay for virtual method.
There was a problem hiding this comment.
Done. Changed to List<MshResolvedExpressionParameterAssociation> in 0e53e55.
| // Apply ExcludeProperty filter using the centralized method | ||
| activeAssociationList = ViewGenerator.ApplyExcludeFilter(activeAssociationList, _parameters?.excludePropertyFilter); |
There was a problem hiding this comment.
I'd expect we benefit from streaming here too. I mean something like - if we set arguments in constructor and SetupActiveProperties we can get filtered list with ActiveAssociationList property.
There was a problem hiding this comment.
Thank you for the suggestion.
Could you clarify the design you have in mind? I want to make sure I understand correctly.
Currently, ComplexViewObjectBrowser is an independent class (not inheriting from ViewGenerator). It processes nested objects by calling DisplayObject recursively - each call handles a different PSObject and creates a new association list via AssociationManager.SetupActiveProperties.
Are you suggesting to:
- Extend
AssociationManager.SetupActivePropertiesto accept an optionalexcludeFilterparameter? - Or restructure
ComplexViewObjectBrowserto useViewGenerator'sActiveAssociationListpattern?
Also, replacing this recursive structure with an iterative approach using explicit stack might enable further streaming optimizations, but that would be a larger architectural change. Should I file a separate issue for that?
There was a problem hiding this comment.
I don't think it's worth it. It's better to spend time on something more useful.
There was a problem hiding this comment.
Thank you for the feedback. I'll keep the current implementation.
| } | ||
|
|
||
| this.activeAssociationList = null; | ||
| ResetActiveAssociationList(); |
There was a problem hiding this comment.
Looks like disposing pattern. But I guess it is too many changes to follow the pattern.
| protected List<MshResolvedExpressionParameterAssociation> GetActiveAssociationList(PSObject so) | ||
| { | ||
| if (this.parameters is not null && this.parameters.excludePropertyFilter is not null) | ||
| if (_activeAssociationList is null) |
There was a problem hiding this comment.
I see here possible problem. If so parameter was changed we returned old list.
Does it make sense to keep the list and manually reset it in ResetActiveAssociationList()?
If yes we should check for so change.
There was a problem hiding this comment.
Thank you for pointing this out.
You're right. The reset pattern wasn't necessary - I've removed it.
Base class now provides BuildActiveAssociationList(PSObject so) which always builds a new list (no caching). Only Table caches locally since it needs to retain the list across method calls.
Changes in ca18256:
FormatViewGenerator.cs: Removed_activeAssociationList,ActiveAssociationList,ResetActiveAssociationList(); renamedGetActiveAssociationList→BuildActiveAssociationListFormatViewGenerator_Table.cs: Added local_activeAssociationListandActiveAssociationListFormatViewGenerator_List.cs,FormatViewGenerator_Wide.cs: UseBuildActiveAssociationList()directly, removedResetcalls
There was a problem hiding this comment.
Pull request overview
This PR successfully refactors the ExcludeProperty filter application logic across the formatting subsystem using the template method design pattern. The changes centralize filter logic that was previously duplicated across multiple ViewGenerator subclasses, making it structurally impossible for future implementations to forget applying the filter.
Key Changes
- Template method pattern in ViewGenerator base class: Introduces
BuildActiveAssociationList()that orchestrates the property resolution and filtering, delegating raw list building to subclass-specificBuildRawAssociationList()overrides - Consistent filter application:
ApplyExcludeFilter()is now a reusable internal static method used by both the template method pattern and ComplexViewObjectBrowser - Simplified subclass implementations: TableViewGenerator, ListViewGenerator, and WideViewGenerator now only override
BuildRawAssociationList()with their format-specific logic, leaving filtering to the base class
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| FormatViewGenerator.cs | Adds template method pattern infrastructure: BuildRawAssociationList() virtual method, BuildActiveAssociationList() orchestration method, and ApplyExcludeFilter() static utility |
| FormatViewGenerator_Table.cs | Overrides BuildRawAssociationList() with table-specific property expansion logic; maintains private cache of filtered association list; refactors LimitAssociationListSize() to be static |
| FormatViewGenerator_List.cs | Overrides BuildRawAssociationList() delegating to AssociationManager.SetupActiveProperties(); removes per-object manual filter application |
| FormatViewGenerator_Wide.cs | Overrides BuildRawAssociationList() with wide-specific property expansion; removes per-object manual filter application |
| FormatViewGenerator_Complex.cs | Replaces inline LINQ filtering with centralized ViewGenerator.ApplyExcludeFilter() call; removes unused System.Linq import |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// <summary> | ||
| /// Gets the cached active association list. | ||
| /// </summary> | ||
| private List<MshResolvedExpressionParameterAssociation> ActiveAssociationList => _activeAssociationList; |
There was a problem hiding this comment.
The ActiveAssociationList property is redundant as it's a simple getter for the _activeAssociationList field within the same private scope. Consider removing this property and accessing _activeAssociationList directly throughout the class to reduce unnecessary indirection.
| /// <summary> | |
| /// Gets the cached active association list. | |
| /// </summary> | |
| private List<MshResolvedExpressionParameterAssociation> ActiveAssociationList => _activeAssociationList; | |
| // Removed redundant property ActiveAssociationList. |
There was a problem hiding this comment.
Good catch. Removed the redundant property and now accessing _activeAssociationList directly, consistent with how _tableBody is used in this class. (569f600)
| var propertyList = parameters?.mshParameterList; | ||
| var excludeFilter = parameters?.excludePropertyFilter; | ||
| var rawList = BuildRawAssociationList(so, propertyList); |
There was a problem hiding this comment.
Please replace var-s with real type.
|
@yotsuda Thanks! Rest of comments are minor. Also you could consider to turn on nullability. (It would be great in any code.) |
|
@daxian-dbw Ah! I'm used to them closing automatically. :-) Thanks for reminding me! I closed the issue. |
PR Summary
This PR centralizes the
ExcludePropertyfilter application logic in theViewGeneratorbase class, addressing the enhancement requested in #26568.Changes
ViewGenerator base class:
activeAssociationListfromprotectedfield toprivatefield (_activeAssociationList)ActiveAssociationListread-only property (returnsIReadOnlyList<>for safety)BuildRawAssociationList(PSObject so, List<MshParameter> propertyList)virtual method for subclasses to overrideGetActiveAssociationList()template method that extracts bothpropertyListandexcludeFilterfromparametersand passes them explicitly to the respective methodsResetActiveAssociationList()method for List/Wide per-object processinginternal static ApplyExcludeFilter()for reuse in ComplexViewObjectBrowserTable/List/Wide ViewGenerators:
BuildRawAssociationList(PSObject so, List<MshParameter> propertyList)to provide cmdlet-specific property expansionparametersfield directly; they receivepropertyListas a method parameterComplex ViewGenerator:
ViewGenerator.ApplyExcludeFilter()callusing System.Linq;(no longer needed)PR Context
PR #26514 added the
-ExcludePropertyparameter to Format-* cmdlets. However, each ViewGenerator subclass had to manually callApplyExcludePropertyFilter()after building the association list. This approach had two issues:This PR addresses these concerns by:
_activeAssociationListprivate so subclasses cannot directly assign to itGetActiveAssociationList()which automatically applies the filterBuildRawAssociationList()that subclasses overrideNote:
ComplexViewGeneratoruses a recursive architecture withComplexViewObjectBrowser(a separate class), so it cannot fully adopt the template method pattern. However, code duplication is eliminated by reusingViewGenerator.ApplyExcludeFilter().Design Decision: Explicit Parameter Passing
The template method
GetActiveAssociationList()extracts bothpropertyListandexcludeFilterfrom theparametersfield at the same level, then passes them explicitly to the respective methods:This design ensures:
propertyListandexcludeFilterare retrieved at the same abstraction levelpropertyListas a parameter instead of accessingparametersdirectlyBuildRawAssociationList()handles property expansion;ApplyExcludeFilter()handles filteringDesign Decision: Lazy Initialization vs Constructor
The
ViewGeneratorclass uses anInitialize()method pattern rather than a constructor, which is an existing design in this codebase. Within this pattern, the association list is built lazily viaGetActiveAssociationList()rather than eagerly inInitialize()because different subclasses have different timing requirements:Initialize()The lazy initialization pattern with
GetActiveAssociationList()andResetActiveAssociationList()provides flexibility for subclasses to call these methods at the appropriate time for their specific formatting needs.Design Decision:
virtualvsabstractforBuildRawAssociationList()Here is the mapping of cmdlets to ViewGenerator classes and their template method adoption:
Format-TableTableViewGeneratorBuildRawAssociationList()Format-ListListViewGeneratorBuildRawAssociationList()Format-WideWideViewGeneratorBuildRawAssociationList()Format-CustomComplexViewGeneratorBuildRawAssociationList(); uses recursive architectureThe general pattern is that subclasses must implement
BuildRawAssociationList().ComplexViewGenerator(used byFormat-Custom) is the exception because it delegates to a separate class for recursive object traversal.Currently,
BuildRawAssociationList()is declared asvirtualwith a default implementation that returnsnull. I'd like feedback on whether this should beabstractinstead.virtual(current)ComplexViewGeneratorNullReferenceExceptionat runtimeabstractComplexViewGeneratorto have an explicit opt-out implementation (e.g.,BuildRawAssociationList()returnsnullor throwsNotSupportedException)If
abstractis preferred, I can update the PR.PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright header