@@ -715,3 +715,209 @@ Describe 'Base type has abstract properties' -Tags "CI" {
715
715
$failure.Exception.Message | Should - BeLike " *'get_Exists'*"
716
716
}
717
717
}
718
+
719
+ Describe ' Classes inheritance with protected and protected internal members in base class' - Tags ' CI' {
720
+
721
+ BeforeAll {
722
+ Set-StrictMode - Version 3
723
+ $c1DefinitionProtectedInternal = @'
724
+ public class C1ProtectedInternal
725
+ {
726
+ protected internal string InstanceField = "C1_InstanceField";
727
+ protected internal string InstanceProperty { get; set; } = "C1_InstanceProperty";
728
+ protected internal string InstanceMethod() { return "C1_InstanceMethod"; }
729
+
730
+ protected internal virtual string VirtualProperty1 { get; set; } = "C1_VirtualProperty1";
731
+ protected internal virtual string VirtualProperty2 { get; set; } = "C1_VirtualProperty2";
732
+ protected internal virtual string VirtualMethod1() { return "C1_VirtualMethod1"; }
733
+ protected internal virtual string VirtualMethod2() { return "C1_VirtualMethod2"; }
734
+
735
+ public string CtorUsed { get; set; }
736
+ public C1ProtectedInternal() { CtorUsed = "default ctor"; }
737
+ protected internal C1ProtectedInternal(string p1) { CtorUsed = "C1_ctor_1args:" + p1; }
738
+ }
739
+ '@
740
+ $c2DefinitionProtectedInternal = @'
741
+ class C2ProtectedInternal : C1ProtectedInternal {
742
+ C2ProtectedInternal() : base() { $this.VirtualProperty2 = 'C2_VirtualProperty2' }
743
+ C2ProtectedInternal([string]$p1) : base($p1) { $this.VirtualProperty2 = 'C2_VirtualProperty2' }
744
+
745
+ [string]GetInstanceField() { return $this.InstanceField }
746
+ [string]SetInstanceField([string]$value) { $this.InstanceField = $value; return $this.InstanceField }
747
+ [string]GetInstanceProperty() { return $this.InstanceProperty }
748
+ [string]SetInstanceProperty([string]$value) { $this.InstanceProperty = $value; return $this.InstanceProperty }
749
+ [string]CallInstanceMethod() { return $this.InstanceMethod() }
750
+
751
+ [string]GetVirtualProperty1() { return $this.VirtualProperty1 }
752
+ [string]SetVirtualProperty1([string]$value) { $this.VirtualProperty1 = $value; return $this.VirtualProperty1 }
753
+ [string]CallVirtualMethod1() { return $this.VirtualMethod1() }
754
+
755
+ [string]$VirtualProperty2
756
+ [string]VirtualMethod2() { return 'C2_VirtualMethod2' }
757
+ # Note: Overriding a virtual property in a derived PowerShell class prevents access to the
758
+ # base property via simple typecast ([base]$this).VirtualProperty2.
759
+ [string]GetVirtualProperty2() { return $this.VirtualProperty2 }
760
+ [string]SetVirtualProperty2([string]$value) { $this.VirtualProperty2 = $value; return $this.VirtualProperty2 }
761
+ [string]CallVirtualMethod2Base() { return ([C1ProtectedInternal]$this).VirtualMethod2() }
762
+ [string]CallVirtualMethod2Derived() { return $this.VirtualMethod2() }
763
+
764
+ [string]GetInstanceMemberDynamic([string]$name) { return $this.$name }
765
+ [string]SetInstanceMemberDynamic([string]$name, [string]$value) { $this.$name = $value; return $this.$name }
766
+ [string]CallInstanceMemberDynamic([string]$name) { return $this.$name() }
767
+ }
768
+
769
+ [C2ProtectedInternal]
770
+ '@
771
+
772
+ Add-Type - TypeDefinition $c1DefinitionProtectedInternal
773
+ Add-Type - TypeDefinition (($c1DefinitionProtectedInternal -creplace ' C1ProtectedInternal' , ' C1Protected' ) -creplace ' protected internal' , ' protected' )
774
+
775
+ $testCases = @ (
776
+ @ { accessType = ' protected' ; derivedType = Invoke-Expression ($c2DefinitionProtectedInternal -creplace ' ProtectedInternal' , ' Protected' ) }
777
+ @ { accessType = ' protected internal' ; derivedType = Invoke-Expression $c2DefinitionProtectedInternal }
778
+ )
779
+ }
780
+
781
+ AfterAll {
782
+ Set-StrictMode - Off
783
+ }
784
+
785
+ Context ' Derived class can access instance base class members' {
786
+
787
+ It ' can call protected internal .NET method Object.MemberwiseClone()' {
788
+ class CNetMethod {
789
+ [string ]$Foo
790
+ [object ]CloneIt() { return $this.MemberwiseClone () }
791
+ }
792
+ $c1 = [CNetMethod ]::new()
793
+ $c1.Foo = ' bar'
794
+ $c2 = $c1.CloneIt ()
795
+ $c2.Foo | Should - Be ' bar'
796
+ }
797
+
798
+ It ' can call <accessType> base ctor' - TestCases $testCases {
799
+ param ($derivedType )
800
+ $derivedType ::new(' foo' ).CtorUsed | Should - Be ' C1_ctor_1args:foo'
801
+ }
802
+
803
+ It ' can access <accessType> base field' - TestCases $testCases {
804
+ param ($derivedType )
805
+ $c2 = $derivedType ::new()
806
+ $c2.GetInstanceField () | Should - Be ' C1_InstanceField'
807
+ $c2.SetInstanceField (' foo_InstanceField' ) | Should - Be ' foo_InstanceField'
808
+ }
809
+
810
+ It ' can access <accessType> base property' - TestCases $testCases {
811
+ param ($derivedType )
812
+ $c2 = $derivedType ::new()
813
+ $c2.GetInstanceProperty () | Should - Be ' C1_InstanceProperty'
814
+ $c2.SetInstanceProperty (' foo_InstanceProperty' ) | Should - Be ' foo_InstanceProperty'
815
+ }
816
+
817
+ It ' can call <accessType> base method' - TestCases $testCases {
818
+ param ($derivedType )
819
+ $derivedType ::new().CallInstanceMethod() | Should - Be ' C1_InstanceMethod'
820
+ }
821
+
822
+ It ' can access <accessType> virtual base property' - TestCases $testCases {
823
+ param ($derivedType )
824
+ $c2 = $derivedType ::new()
825
+ $c2.GetVirtualProperty1 () | Should - Be ' C1_VirtualProperty1'
826
+ $c2.SetVirtualProperty1 (' foo_VirtualProperty1' ) | Should - Be ' foo_VirtualProperty1'
827
+ }
828
+
829
+ It ' can call <accessType> virtual base method' - TestCases $testCases {
830
+ param ($derivedType )
831
+ $derivedType ::new().CallVirtualMethod1() | Should - Be ' C1_VirtualMethod1'
832
+ }
833
+ }
834
+
835
+ Context ' Derived class can override virtual base class members' {
836
+
837
+ It ' can override <accessType> virtual base property' - TestCases $testCases {
838
+ param ($derivedType )
839
+ $c2 = $derivedType ::new()
840
+ $c2.GetVirtualProperty2 () | Should - Be ' C2_VirtualProperty2'
841
+ $c2.SetVirtualProperty2 (' foo_VirtualProperty2' ) | Should - Be ' foo_VirtualProperty2'
842
+ }
843
+
844
+ It ' can override <accessType> virtual base method' - TestCases $testCases {
845
+ param ($derivedType )
846
+ $c2 = $derivedType ::new()
847
+ $c2.CallVirtualMethod2Base () | Should - Be ' C1_VirtualMethod2'
848
+ $c2.CallVirtualMethod2Derived () | Should - Be ' C2_VirtualMethod2'
849
+ }
850
+ }
851
+
852
+ Context ' Derived class can access instance base class members dynamically' {
853
+
854
+ It ' can access <accessType> base fields and properties' - TestCases $testCases {
855
+ param ($derivedType )
856
+ $c2 = $derivedType ::new()
857
+ $c2.GetInstanceMemberDynamic (' InstanceField' ) | Should - Be ' C1_InstanceField'
858
+ $c2.GetInstanceMemberDynamic (' InstanceProperty' ) | Should - Be ' C1_InstanceProperty'
859
+ $c2.GetInstanceMemberDynamic (' VirtualProperty1' ) | Should - Be ' C1_VirtualProperty1'
860
+ $c2.SetInstanceMemberDynamic (' InstanceField' , ' foo1' ) | Should - Be ' foo1'
861
+ $c2.SetInstanceMemberDynamic (' InstanceProperty' , ' foo2' ) | Should - Be ' foo2'
862
+ $c2.SetInstanceMemberDynamic (' VirtualProperty1' , ' foo3' ) | Should - Be ' foo3'
863
+ }
864
+
865
+ It ' can call <accessType> base methods' - TestCases $testCases {
866
+ param ($derivedType )
867
+ $c2 = $derivedType ::new()
868
+ $c2.CallInstanceMemberDynamic (' InstanceMethod' ) | Should - Be ' C1_InstanceMethod'
869
+ $c2.CallInstanceMemberDynamic (' VirtualMethod1' ) | Should - Be ' C1_VirtualMethod1'
870
+ }
871
+ }
872
+
873
+ Context ' Base class members are not accessible outside class scope' {
874
+
875
+ BeforeAll {
876
+ $instanceTest = {
877
+ $c2 = $derivedType ::new()
878
+ { $null = $c2.InstanceField } | Should - Throw - ErrorId ' PropertyNotFoundStrict'
879
+ { $null = $c2.InstanceProperty } | Should - Throw - ErrorId ' PropertyNotFoundStrict'
880
+ { $null = $c2.VirtualProperty1 } | Should - Throw - ErrorId ' PropertyNotFoundStrict'
881
+ { $c2.InstanceField = ' foo' } | Should - Throw - ErrorId ' PropertyAssignmentException'
882
+ { $c2.InstanceProperty = ' foo' } | Should - Throw - ErrorId ' PropertyAssignmentException'
883
+ { $c2.VirtualProperty1 = ' foo' } | Should - Throw - ErrorId ' PropertyAssignmentException'
884
+ { $derivedType ::new().InstanceMethod() } | Should - Throw - ErrorId ' MethodNotFound'
885
+ { $derivedType ::new().VirtualMethod1() } | Should - Throw - ErrorId ' MethodNotFound'
886
+ foreach ($name in @ (' InstanceField' , ' InstanceProperty' , ' VirtualProperty1' )) {
887
+ { $null = $c2 .$name } | Should - Throw - ErrorId ' PropertyNotFoundStrict'
888
+ { $c2 .$name = ' foo' } | Should - Throw - ErrorId ' PropertyAssignmentException'
889
+ }
890
+ foreach ($name in @ (' InstanceMethod' , ' VirtualMethod1' )) {
891
+ { $c2 .$name () } | Should - Throw - ErrorId ' MethodNotFound'
892
+ }
893
+ }
894
+ $c3UnrelatedType = Invoke-Expression @"
895
+ class C3Unrelated {
896
+ [void]RunInstanceTest([type]`$ derivedType) { $instanceTest }
897
+ }
898
+ [C3Unrelated]
899
+ "@
900
+ $negativeTestCases = $testCases.ForEach ({
901
+ $item = $_.Clone ()
902
+ $item [' scopeType' ] = ' null scope'
903
+ $item [' classScope' ] = $null
904
+ $item
905
+ $item = $_.Clone ()
906
+ $item [' scopeType' ] = ' unrelated class scope'
907
+ $item [' classScope' ] = $c3UnrelatedType
908
+ $item
909
+ })
910
+ }
911
+
912
+ It ' cannot access <accessType> instance base members in <scopeType>' - TestCases $negativeTestCases {
913
+ param ($derivedType , $classScope )
914
+ if ($null -eq $classScope ) {
915
+ $instanceTest.Invoke ()
916
+ }
917
+ else {
918
+ $c3 = $classScope ::new()
919
+ $c3.RunInstanceTest ($derivedType )
920
+ }
921
+ }
922
+ }
923
+ }
0 commit comments