From 7a51b446e5cc428efd634e8a8a38271156cc182b Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 7 Jun 2017 18:00:03 -0700 Subject: [PATCH] Support creating PS class instance that is not bound to any runspace (#3871) --- .../engine/runtime/Operations/ClassOps.cs | 9 ++- .../Scripting.Classes.MiscOps.Tests.ps1 | 71 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs b/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs index f707df0679..ba5dda9ff0 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs @@ -70,12 +70,17 @@ namespace System.Management.Automation.Internal /// It's not intended to be a public API, but because we generate type in a different assembly it has to be public. /// Return type should be SessionStateInternal, but it violates accessibility consistency, so we use object. /// + /// + /// By default, PowerShell class instantiation usually happens in the same Runspace where the class is defined. In + /// that case, the created instance will be bound to the session state used to define that class in the Runspace. + /// However, if the instantiation happens in a different Runspace where the class is not defined, then the created + /// instance won't be bound to any session state. + /// /// SessionStateInternal public object GetSessionState() { SessionStateInternal ss = null; - bool found = _stateMap.TryGetValue(Runspace.DefaultRunspace, out ss); - Diagnostics.Assert(found, "We always should be able to find corresponding SessionState"); + _stateMap.TryGetValue(Runspace.DefaultRunspace, out ss); return ss; } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 index 26968ecfb1..08b97f1a37 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 @@ -43,4 +43,75 @@ Describe 'Misc Test' -Tags "CI" { [C1]::new().Bar() | should be "1;2;3;" } } + + Context 'Class instantiation' { + Class C1 { + [string] Foo() { + return (Get-TestText) + } + } + + BeforeAll { + $ExpectedTextFromBoundInstance = "Class C1 was defined in this Runspace" + $ExpectedTextFromUnboundInstance = "New Runspace without class C1 defined" + + ## Define 'Get-TestText' in the current Runspace + function Get-TestText { return $ExpectedTextFromBoundInstance } + + $NewRunspaceFunctionDefinitions = @" + ## Define 'Get-TestText' in the new Runspace + function Get-TestText { return '$ExpectedTextFromUnboundInstance' } + + ## Define the function to create an instance of the given type using the default constructor + function New-UnboundInstance([Type]`$type) { `$type::new() } + + ## Define the function to call 'Foo()' on the given C1 instance, and return the result + function Run-Foo(`$C1Instance) { `$C1Instance.Foo() } +"@ + ## Create a new Runspace and define helper functions in it + $powershell = [powershell]::Create() + $powershell.AddScript($NewRunspaceFunctionDefinitions).Invoke() > $null + $powershell.Commands.Clear() + + function InstantiateInNewRunspace([Type]$type) { + try { + $result = $powershell.AddCommand("New-UnboundInstance").AddParameter("type", $type).Invoke() + $result.Count | Should Be 1 > $null + return $result[0] + } finally { + $powershell.Commands.Clear() + } + } + + function RunFooInNewRunspace($instance) { + try { + $result = $powershell.AddCommand("Run-Foo").AddParameter("C1Instance", $instance).Invoke() + $result.Count | Should Be 1 > $null + return $result[0] + } finally { + $powershell.Commands.Clear() + } + } + } + + AfterAll { + $powershell.Dispose() + } + + It "Create instance that is bound to a SessionState" { + $instance = [C1]::new() + ## For a bound class instance, the execution of an instance method is + ## done in the Runspace/SessionState the instance is bound to. + $instance.Foo() | Should Be $ExpectedTextFromBoundInstance + RunFooInNewRunspace $instance | Should Be $ExpectedTextFromBoundInstance + } + + It "Create instance that is NOT bound to a SessionState" { + $instance = InstantiateInNewRunspace ([C1]) + ## For an unbound class instance, the execution of an instance method is done in + ## the Runspace/SessionState where the call to the instance method is made. + $instance.Foo() | Should Be $ExpectedTextFromBoundInstance + RunFooInNewRunspace $instance | Should Be $ExpectedTextFromUnboundInstance + } + } }