-
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
Closed
Closed
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d6a0af6
Implement PSObject's IConvertible
iSazonov 3a57fe7
Fix style issues
iSazonov 3299e55
Implement PSObject's IConvertible
iSazonov 37bf86a
Fix style issues
iSazonov 39d8b7a
Merge branch 'iconvertible' of https://github.com/iSazonov/PowerShell…
iSazonov a2800d7
Fix ToString()
iSazonov 8225d1b
Fix test
iSazonov 2ca8ee3
Fix type converter
iSazonov 8c1df18
Improve GetTypeCode()
iSazonov 179c0ef
Improve GetTypeCode() 2
iSazonov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
src/System.Management.Automation/engine/MshObjectIConvertible.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| namespace System.Management.Automation | ||
| { | ||
| /// <summary> | ||
| /// <see cref="PSObject"/> implements <see cref="System.IConvertible"/> interface for its base object. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// If <see cref="PSObject"/> wraps a value type implementing <see cref="System.IConvertible"/> | ||
| /// it provides that functionality too. | ||
| /// </remarks> | ||
| /// <exception cref="System.InvalidCastException">The base object's implementation cannot convert and throws.</exception> | ||
| public partial class PSObject : IConvertible | ||
| { | ||
| /// <summary> | ||
| /// Returns the type code of the base object wrapped by the <see cref="PSObject"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// If the base object is <see cref="PSObject"/> the method returns <see cref="TypeCode.Object"/> | ||
| /// i.e. it does not implement <see cref="System.IConvertible"/>. | ||
| /// Otherwise, the result is the type code of the base object, | ||
| /// as determined by the base object's implementation of <see cref="System.IConvertible"/>. | ||
| /// </remarks> | ||
| /// <returns>Returns <see cref="System.TypeCode"/> of the PSObject.</returns> | ||
| public TypeCode GetTypeCode() | ||
| { | ||
| object? obj = PSObject.Base(this); | ||
|
|
||
| // Take into account PSObject and all derived classes like InternalPSObject. | ||
| return obj is null || typeof(PSObject).IsAssignableFrom(obj.GetType()) ? TypeCode.Object : Convert.GetTypeCode(obj); | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public bool ToBoolean(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToBoolean(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public char ToChar(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToChar(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public sbyte ToSByte(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToSByte(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public byte ToByte(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToByte(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public short ToInt16(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToInt16(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public ushort ToUInt16(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToUInt16(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public int ToInt32(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToInt32(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public uint ToUInt32(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToUInt32(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public long ToInt64(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToInt64(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public ulong ToUInt64(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToUInt64(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public float ToSingle(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToSingle(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public double ToDouble(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToDouble(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public decimal ToDecimal(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToDecimal(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public DateTime ToDateTime(IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToDateTime(provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public string ToString(IFormatProvider? provider) => this.ToString(format: null, provider); | ||
|
|
||
| /// <inheritdoc/> | ||
| public object ToType(Type conversionType, IFormatProvider? provider) => GetIConvertibleOrThrow(this).ToType(conversionType, provider); | ||
|
|
||
| private static IConvertible GetIConvertibleOrThrow(PSObject pso) | ||
| { | ||
| object obj = PSObject.Base(pso); | ||
| if (obj is PSObject || obj is not IConvertible value) | ||
| { | ||
| ThrowInvalidCastException(); | ||
|
|
||
| // We have to use the workaround | ||
| // because of C# limitations (it doesn't know that ThrowInvalidCastException() is never returned). | ||
| // DoesNotReturn don't address the scenario. | ||
| // See https://github.com/dotnet/runtime/issues/79647#issuecomment-1351370392 | ||
| return null!; | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
| [DoesNotReturn] | ||
| private static void ThrowInvalidCastException() => throw new InvalidCastException(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Management.Automation; | ||
| using Xunit; | ||
|
|
||
| namespace PSTests.Parallel | ||
| { | ||
| [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1121:UseBuiltInTypeAlias", Justification = "Intentionally not using built-in alias.")] | ||
| public static class PSObjectIConvertibleTests | ||
| { | ||
| [Fact] | ||
| public static void TestBool() | ||
| { | ||
| var value = (object)true; | ||
| var wrappedValue = new PSObject(true); | ||
| Assert.Equal(value, wrappedValue.ToBoolean(provider: null)); | ||
| Assert.Equal(typeof(bool).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestChar() | ||
| { | ||
| var value = (object)'K'; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToChar(provider: null)); | ||
| Assert.Equal(typeof(char).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToSByte() | ||
| { | ||
| var value = (object)(SByte)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToSByte(provider: null)); | ||
| Assert.Equal(typeof(SByte).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToByte() | ||
| { | ||
| var value = (object)(Byte)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToByte(provider: null)); | ||
| Assert.Equal(typeof(Byte).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToInt16() | ||
| { | ||
| var value = (object)(Int16)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToInt16(provider: null)); | ||
| Assert.Equal(typeof(Int16).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToUInt16() | ||
| { | ||
| var value = (object)(UInt16)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToUInt16(provider: null)); | ||
| Assert.Equal(typeof(UInt16).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToInt32() | ||
| { | ||
| var value = (object)(Int32)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToInt32(provider: null)); | ||
| Assert.Equal(typeof(Int32).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToUInt32() | ||
| { | ||
| var value = (object)(UInt32)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToUInt32(provider: null)); | ||
| Assert.Equal(typeof(UInt32).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToInt64() | ||
| { | ||
| var value = (object)(Int64)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToInt64(provider: null)); | ||
| Assert.Equal(typeof(Int64).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToUInt64() | ||
| { | ||
| var value = (object)(UInt64)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToUInt64(provider: null)); | ||
| Assert.Equal(typeof(UInt64).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToSingle() | ||
| { | ||
| var value = (object)(Single)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToSingle(provider: null)); | ||
| Assert.Equal(typeof(Single).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToDouble() | ||
| { | ||
| var value = (object)(Double)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToDouble(provider: null)); | ||
| Assert.Equal(typeof(Double).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToDecimal() | ||
| { | ||
| var value = (object)(Decimal)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToDecimal(provider: null)); | ||
| Assert.Equal(typeof(Decimal).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToDateTime() | ||
| { | ||
| var value = (object)DateTime.UtcNow; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToDateTime(provider: null)); | ||
| Assert.Equal(typeof(DateTime).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToString() | ||
| { | ||
| var value = (object)"1"; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(value, wrappedValue.ToString(provider: null)); | ||
| Assert.Equal(typeof(String).GetTypeCode(), wrappedValue.GetTypeCode()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestToType() | ||
| { | ||
| var value = (object)1; | ||
| var wrappedValue = new PSObject(value); | ||
| Assert.Equal(typeof(bool), wrappedValue.ToType(typeof(bool), provider: null).GetType()); | ||
| Assert.Equal(typeof(char), wrappedValue.ToType(typeof(char), provider: null).GetType()); | ||
| Assert.Equal(typeof(SByte), wrappedValue.ToType(typeof(SByte), provider: null).GetType()); | ||
| Assert.Equal(typeof(Byte), wrappedValue.ToType(typeof(Byte), provider: null).GetType()); | ||
| Assert.Equal(typeof(Int16), wrappedValue.ToType(typeof(Int16), provider: null).GetType()); | ||
| Assert.Equal(typeof(UInt16), wrappedValue.ToType(typeof(UInt16), provider: null).GetType()); | ||
| Assert.Equal(typeof(Int32), wrappedValue.ToType(typeof(Int32), provider: null).GetType()); | ||
| Assert.Equal(typeof(UInt32), wrappedValue.ToType(typeof(UInt32), provider: null).GetType()); | ||
| Assert.Equal(typeof(Int64), wrappedValue.ToType(typeof(Int64), provider: null).GetType()); | ||
| Assert.Equal(typeof(UInt64), wrappedValue.ToType(typeof(UInt64), provider: null).GetType()); | ||
| Assert.Equal(typeof(Single), wrappedValue.ToType(typeof(Single), provider: null).GetType()); | ||
| Assert.Equal(typeof(Double), wrappedValue.ToType(typeof(Double), provider: null).GetType()); | ||
| Assert.Equal(typeof(Decimal), wrappedValue.ToType(typeof(Decimal), provider: null).GetType()); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(DateTime), provider: null)); | ||
| Assert.Equal(typeof(String), wrappedValue.ToType(typeof(String), provider: null).GetType()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public static void TestNoLoopAndStackOverflow() | ||
| { | ||
| // PSObject.Base(wrappedValue) returns PSObject. | ||
| // It is infinite loop. | ||
| // so PSObject IConvertible implementation should throw | ||
| // with InvalidCastException instead of StackOverflowException. | ||
| var wrappedValue = new PSObject(); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToBoolean(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToChar(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToSByte(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToByte(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToInt16(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToUInt16(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToInt32(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToUInt32(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToInt64(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToUInt64(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToSingle(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToDouble(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToDecimal(provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToDateTime(provider: null)); | ||
|
|
||
| Assert.Equal(string.Empty, wrappedValue.ToString(provider: null)); | ||
|
|
||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(bool), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(char), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(SByte), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Byte), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Int16), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(UInt16), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Int32), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(UInt32), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Int64), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(UInt64), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Single), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Double), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(Decimal), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(DateTime), provider: null)); | ||
| Assert.Throws<InvalidCastException>(() => wrappedValue.ToType(typeof(String), provider: null)); | ||
|
|
||
| Assert.Equal(TypeCode.Object, wrappedValue.GetTypeCode()); | ||
| } | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
throwblocks inlining. .Net suggests long term patterns like ArgumentNullException.ThrowIf(). We can benefit from this too.