Move PSVersionInfo.PSVersionName to first place#3562
Move PSVersionInfo.PSVersionName to first place#3562daxian-dbw merged 9 commits intoPowerShell:masterfrom
Conversation
|
Quoted from @rkeithhill's comment
|
|
Ok, tomorrow. |
8c116eb to
6dd8c70
Compare
| keyList.Sort(); | ||
| MoveItemOnFirstPlace(PSVersionInfo.PSEditionName, keyList); | ||
| MoveItemOnFirstPlace(PSVersionInfo.PSVersionName, keyList); | ||
| return keyList.ToArray(); |
There was a problem hiding this comment.
This implementation is a little bit heavy, it involves shuffling keyList's elements for 3 times (sort() and 2 MoveItemOnFirstPlace) + 2 collection allocation (ArrayList and ToArray).
Maybe the following pseudo code would be better?
Array arr = new string[base.Keys.Count];
int index = 2;
foreach (string key in base.Keys)
{
switch (key)
case PSVersionInfo.PSVersionName:
arr[0] = key;
break;
case PSVersionInfo.PSEditionName:
arr[1] = key;
break;
default:
arr[index++] = key;
break;
}
Array.Sort(arr, 2, arr.Length - 2; /* the equalityComparer passed in the constructor */);
return arr;It allocate only one collection, and elements in arr only get moved around once, within the Sort method.
The pseudo code is still not perfect, because we will create an array object every time Keys is accessed. If we want to go further to avoid that, we can create our own type that implements ICollectionand is immutable from outside (no public members to mutate it, just like the type of object that Hashtable.Keys returns -- Hashtable+KeyCollection). Then we can keep one instance of it with PSVersionHashTable, and always reuse and return the same instance from Keys property.
There was a problem hiding this comment.
Thanks for help with the code!
I added the optimized sorting and description.
If we do Keys read-only we should do PSVersionTable read-only too. And it is a breaking change - currently we can add in PSVersionTable own version strings. For example, third-party module can add its strings in PSVersionTable. I think it's not worth and we can live without the Keys cache. What are your thoughts?
There was a problem hiding this comment.
PSVersionTable doesn't need to be read-only for the Keys to be read-only. Hashtable.Keys returns an object of System.Collections.Hashtable+KeyCollection, which expose no public members for you to mutate it.
This would be a pure optimization and is not required for this fix.
| s_psVersionTable[PSVersionInfo.PSEditionName] = PSEditionValue; | ||
| s_psVersionTable["BuildVersion"] = GetBuildVersion(); | ||
| s_psVersionTable["GitCommitId"] = GetCommitInfo(); | ||
| s_psVersionTable["PSCompatibleVersions"] = new Version[] { s_psV1Version, s_psV2Version, s_psV3Version, s_psV4Version, s_psV5Version, s_psV51Version, s_psV6Version }; |
There was a problem hiding this comment.
Why not have const variables for other keys: "BuildVersion", "GitCommitId" and "PSCompatibleVersions"? 😄
There was a problem hiding this comment.
I did not want to distract the unrelated changes. But I agree to bring it into line if you don't mind.
|
@iSazonov, the implementation will break in 2 cases:
I pushed a fix to your branch, and need your help to review the code to see if there are other issues. |
|
Why we want to allow non-string keys? It seems the keys in |
|
And maybe move our logic in custom StringComparer? |
We don't want non-string keys, but user can add any key-value pairs to |
|
If we are faced with the keys of arbitrary type we would use In last commit I also have tried to move sorting logic to custom Comparer. It looks more simple 😄 If you dont agree feel free to revert. |
| public override int Compare(object x, object y) | ||
| { | ||
| string xString = (string)LanguagePrimitives.ConvertTo(x, typeof(string), null); | ||
| string yString = (string)LanguagePrimitives.ConvertTo(y, typeof(string), null); |
There was a problem hiding this comment.
What if x or y are objects but calling LanguagePrimitives.ConvertTo on them returns PSVersion or PSEdition as the converted string?
I still prefer to do sorting only if keys are all strings. If there is a non-string key, we just give up sorting and return base.Keys.
Sorry for being picky with rare cases. Since we are creating a public accessible data structure, we have to consider whatever that might happen.
There was a problem hiding this comment.
This is very useful for me. 😄 The more we did not analyse this deeply before.
All previous options that we had, nothing broke really. The only thing we do here is to change a human output to the screen.
Output (Out-Default) always perform conversion to strings. Even if we have non-string key, it will be converted to a string. And it seems better to even see them sorted.
If a user adds a record with key that is converted to "PSVersion" we see two entries. We could block it but it will be breaking change.
Also we should use LanguagePrimitives.ConvertTo<string>(Value) (#2678 (comment)) to avoid "strange" sorting.
$a=[pscustomobject]@{v1=123}
$a
$a.ToString() # It is empty!
$PSVersionTable[$a]="asdfgh"
$PSVersionTable
Name Value
---- -----
PSVersion 6.0.0-alpha
PSEdition Core
@{v1=123} asdfgh
BuildVersion 3.0.0.0
CLRVersion
GitCommitId v6.0.0-alpha.18-36-gc367cb58d87a84597077fbbe9a5ded5310179878
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
$b=$a | Add-Member -MemberType ScriptMethod -Name ToString -Value {"PSVersion"} -PassThru -Force
$PSVersionTable[$b]="zxcvbn"
$PSVersionTable
Name Value
---- -----
PSVersion zxcvbn
PSVersion 6.0.0-alpha
PSEdition Core
@{v1=123} asdfgh
BuildVersion 3.0.0.0
CLRVersion
GitCommitId v6.0.0-alpha.18-36-gc367cb58d87a84597077fbbe9a5ded5310179878
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0There was a problem hiding this comment.
Sorry @iSazonov, I was busy with some other tasks. I will get back to this one ASAP.
There was a problem hiding this comment.
Good argument. I agree to have the PSVersionTableComparer for the sorting.
| } | ||
| } | ||
|
|
||
| private class PSVersionTableComparer : Comparer<object> |
There was a problem hiding this comment.
Maybe just implement the IComparer interface?
There was a problem hiding this comment.
For PSVersionTable? We only compare keys not PSVersionTables.
There was a problem hiding this comment.
No, for PSVersionTableComparer. ArrayList.Sort(IComparer) takes an IComparer as argument.
| { | ||
| public override int Compare(object x, object y) | ||
| { | ||
| string xString = (string)LanguagePrimitives.ConvertTo(x, typeof(string), null); |
There was a problem hiding this comment.
We should use CultureInfo.CurrentCulture for the formatProvider parameter.
| { | ||
| string xString = (string)LanguagePrimitives.ConvertTo(x, typeof(string), null); | ||
| string yString = (string)LanguagePrimitives.ConvertTo(y, typeof(string), null); | ||
| if (PSVersionInfo.PSVersionName == xString) |
There was a problem hiding this comment.
Instead of ==, we should use PSVersionInfo.PSVersionName.Equals(xString, StringComparison.OrdinalIgnoreCase)
| } | ||
| else | ||
| { | ||
| return xString.CompareTo(yString); |
There was a problem hiding this comment.
Maybe use String.Compare(string strA, string strB, System.StringComparison comparisonType) so that you can specify StringComparison.OrdinalIgnoreCase.
|
#3654 made changes to |
| Array.Sort(arr, StringComparer.OrdinalIgnoreCase); | ||
| return arr; | ||
| ArrayList keyList = new ArrayList(base.Keys); | ||
| keyList.Sort(new PSVersionTableComparer()); |
There was a problem hiding this comment.
PSVersionHashTable can have a static readonly field to hold an instance of PSVersionTableComparer, so as to avoid creating a new comparer every time.
|
@daxian-dbw Conflicts was resolved. Also I moved tests to PSVersionTable.Tests.ps1 file. |
| } | ||
| } | ||
|
|
||
| private static PSVersionTableComparer keysComparer = new PSVersionTableComparer(); |
There was a problem hiding this comment.
Please make it readonly.
It's recommended to put field declarations before constructors, and for a private static field, it should be named with the prfix s_ -- s_keysComparer. See s_psVersionTable in this file for an example.
| } | ||
|
|
||
| private static PSVersionTableComparer keysComparer = new PSVersionTableComparer(); | ||
| private class PSVersionTableComparer : Comparer<object> |
There was a problem hiding this comment.
Like we discussed, PSVersionTableComparer can just implement IComparer due to ArrayList.Sort(IComparer comparer).
| using System.Diagnostics; | ||
| using System.Reflection; | ||
| using System.Collections; | ||
| using System.Collections.Generic; |
There was a problem hiding this comment.
Once you make PSVersionTableComparer implements IComparer instead of deriving from Comparer<object>, this using directive can be removed.
|
@daxian-dbw Thanks for review and usefull comments! |
|
@iSazonov Thanks for the great fix! 👍 |
Based on discussion #3530 (review)
We want
PSVersionon first place and the rest sorted alphabetically