-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Implement PSObject's IConvertible #19170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@daxian-dbw Could you please help? I cannot find where fix PSInvokeMemberBinder. $semver = [System.Management.Automation.SemanticVersion]::new(1, 0, 0)
$ser = [System.Management.Automation.PSSerializer]::Serialize($semver)
$des = [System.Management.Automation.PSSerializer]::Deserialize($ser)
$des.<Ctrl-Space> # Shows all IConvertible methods including ToString() but next fails
$des.ToString()
InvalidOperation: Method invocation failed because [Deserialized.System.Management.Automation.SemanticVersion] does not contain a method named 'ToString'. |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
|
More simple repro: $a = [psobject]::new()
$a.<Ctrl-Tab>
$a.psobject.<Ctrl-Tab> |
| object obj = PSObject.Base(pso); | ||
| if (obj is PSObject || obj is not IConvertible value) | ||
| { | ||
| ThrowInvalidCastException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this method is only used once here, would it be better to simply have this block throw the exception and not have the workaround?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw blocks inlining. .Net suggests long term patterns like ArgumentNullException.ThrowIf(). We can benefit from this too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some debugging but couldn't find the root cause. It cannot resolve the ToString member from the deserialized PSObject instance. The InstanceMethods member of the PSObject instance also contains different entries comparing with when running in a build without this change.
Specifically, when running $des.ToString, the _hasInstanceMember is false with the changes in this PR while it's true when running without.
PowerShell/src/System.Management.Automation/engine/runtime/Binding/Binders.cs
Lines 5209 to 5212 in 6dfba03
| Expression expr = null; | |
| if (_hasInstanceMember && TryGetInstanceMember(target.Value, Name, out memberInfo)) | |
| { | |
| // If there is an instance member, we generate (roughly) the following: |
So, something very confusing may happen in the deserialization/serialization code.
Also, I found you can tab complete the IConvertible interface methods on $des, which is a Deserialized.xxx PSObject, but not on a newly created PSObject [psobject]::new(). But $des.ToBoolean() for example will fail too.
| public TypeCode GetTypeCode() | ||
| { | ||
| object obj = PSObject.Base(this); | ||
| return obj is PSObject ? TypeCode.Object : Convert.GetTypeCode(obj); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public bool ToBoolean(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToBoolean(provider); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All members should be explicit definitions, like bool IConvertible.ToBoolean(IFormatProvider? provider) => ...
However, changing to explicit definitions does NOT address the exception we saw. You get the same error 😦
Eh, Debugger initializes PowerShell caches and hides real steps. As result we don't see details. :-( I added Console.WriteLine in the new code and I did not see they are called. Then I searched for IConvertible and it only exists in PSObject.ToString(). This is not what we need. I searched for IConvertible in .Net and there is nothing in the C# code either. So I assumed that the reason is in the .Net reflection code, somewhere in C++. |
You can find that code in the adaptor code, |
|
The fact that PSObject representing a deserialized .NET object behaves differently from a regular PSObject is very bizarre. |
Fix is to block the branch if fromType is PSObject so that return previous behavior. Alternatively, we could change the IConvertible.ToType() implementation, but through the .Net code this would again take us back to the complex PowerShell type conversion code. This seems unpredictable, dangerous (recursion), and most importantly, unreasonable.
The current implementation calls a method of base type (e.g., ToInt32()) for which the TypeCode is known beforehand. We could call the universal LanguagePrimitives.ConvertTo() method, but we cannot implement a dynamic value for TypeCode because of a contradiction - TypeCode must be known before the conversion, but we can only compute it after the conversion. This also contradicts the documentation requirement to return TypeCode.Object for all other types.
|
|
This PR has Quantification details
Why proper sizing of changes matters
Optimal pull request sizes drive a better predictable PR flow as they strike a
What can I do to optimize my changes
How to interpret the change counts in git diff output
Was this comment helpful? 👍 :ok_hand: :thumbsdown: (Email) |
|
Thinking about this again, I'm not sure if making I think the answer should be a hard "No" ... |
|
We started with a specific user case, namely System.Data makes extensive use of IConvertible. This interface is an old legacy, it will never evolve. .Net team says to use more modern APIs. I also found that ASP.NET also uses IConvertible extensively. |
|
Marked the PR "Engine - WG". Will let the engine WG review. |
|
For additional information about IConvertible see dotnet/runtime#73074 (comment) and below comments. |
|
This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days. |
|
The Engine WG discussed this and decided that we should not implement interfaces that may or may not be implemented on the base object. A primary concern is that a Instead we recommend that the original issue is fixed with either:
|
PR Summary
Fix #17627
Replace #19071
Pester tests come from #19071
Implement IConvertible interface for PSObject.
Since PSObject can wrap an object of any type the implementation is dynamic too: it calls appropriate methods from PSObject's base class.
PR Context
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.(which runs in a different PS Host).