Allow more control on custom type formatting
See original GitHub issueSummary of the new feature/enhancement
A developer might have created some custom type that aims to behave like a primitive or built-in type, but PowerShell does not provide a way to let the developer precisely control formatting of custom types. In particular, the handling of string
is very special — it implements IEnumerable<char>
, but will not be enumerated (into a stream of char
s) when formatted. There is no way for a custom type to be handled like a string
.
Another example. Currently, System.Text.Rune
does not have special handling, and the default formatting is Format-List
instead of ToString()
. Arguably, ToString()
is more appropriate. For a custom type, the only way to force ToString()
formatting is to apply System.Management.Automation.HiddenAttribute
to all instance fields and properties. However, this comes with the downside of referencing PowerShell in the assembly containing the type, but the assembly should be usable even in an environment without PowerShell (and you don’t want to load PowerShell assemblies if you aren’t using it anyway).
The closest we can get for Rune
(or any other custom type not implementing IEnumerable
nor IEnumerator
) is to give it a Custom
view, but it will add blank lines when outputting. Not ideal…
This issue aims to generate discussion on creating mechanisms to have fine-grained control over (default) formatting of a type. Related #9538.
Proposed technical implementation details (optional)
Here is a simple proposal.
- Decouple
Out-Default
intoFormat-Default
andOut-Host
. Today’s PowerShell handlesOut-Default
monolithically. It choosesFormat-Table
orFormat-List
orToString()
-formatting, depending on the number of non-hidden public fields and properties, and then appliesOut-Host
. - In
Format.ps1xml
, allow a new type ofNativeControl
. It specifies a type name, which, if found in loaded assemblies, is used to format the object. Format-Default
chooses the format as follows:- If there is a view defined for this type, use the first available view (which does not have to be
NativeControl
view). However, ifNativeControl
formatter was used to produce the object being formatted (formatting is recursive forNativeControl
views), thenNativeControl
is skipped. - Otherwise, use today’s default formatting choice. (Special handling for
string
; useToString()
if there is no non-hidden public fields or properties; useFormat-Table
if there are only a few; useFormat-List
if there are many.)
- If there is a view defined for this type, use the first available view (which does not have to be
Make the types in Microsoft.PowerShell.Commands.Internal.Format
public, so people can create custom format objects. Provide a new abstract class:
// or another namespace?
namespace System.Management.Automation
{
// The derived class must be stateless, so that
// only one instance is created per PowerShell runspace.
public abstract class NativeFormatter
{
// The returned object is recursively formatted,
// but cannot be sent to the same formatter again.
public abstract object FormatObject(object obj);
}
}
For example, suppose we have a custom string class AsciiString
, then we can create
public sealed class AsciiStringFormatter : NativeFormatter
{
public override object FormatObject(object obj) { return ((AsciiString)obj).ToString(); }
}
This will format an AsciiString
into a string
, which will then be handled by PowerShell natively.
It’s also possible to return IEnumerable
from FormatObject
, which will be expanded by PowerShell, and each resulting object is formatted by their own formatter. Those expanded object could be what is Microsoft.PowerShell.Commands.Internal.Format.GroupEntryData
today.
Why it is important that we implement formatting in another class.
- First, we don’t want the main assembly (e.g., the assembly containing
AsciiString
) to be tied to PowerShell. By allowing it to be implemented in another class, the author can implement the formatter in a separate assembly dedicated for scripting (a kind of add-on to the main assembly). - Second, users (non-developers of the main assembly) can develop their own native formatter if they so wish.
Issue Analytics
- State:
- Created 2 years ago
- Comments:11 (5 by maintainers)
Top GitHub Comments
According to the docs, every line of command is equivalent when appended with
| Out-Host
, so if an object is enumerable, it will only be treated as the enumerated items, not itself.When scripting, there really isn’t much difference between
List<byte>
andbyte[]
(and evenobject[]
with all items being boxedbyte
s). I wouldn’t expect a cmdlet/function that happens to useList
as its intermediate storage and direct return value to be formatted differently. But there is a crucial difference betweenAsciiString : IEnumerable<byte>
andbyte[]
. I would lean towards implementing the first aspect, and leaving the second as-is (an enumerable type cannot be formatted, unless the enumeration depth is exhausted).@mklement0 Thanks for the pointer! I didn’t know
OutOfBand
(looks like this feature is little documented). This definitely solves the problem for non-enumerable types. Yet the issue for enumerable types still stand.Currently, it’s not possible to prevent enumeration unless the type is
string
or the enumeration depth is exhausted.