Is it correct to use the get_Keys() method for collections
Asked Answered
S

1

4

Related to this question: Can I add a key named 'keys' to a hashtable without overriding the 'keys' member , I am actually often using the get_Keys() method as the recommended PSBase property will just move the problem.
In other words, this method appears especially handy for an unknown list of directory keys in case of a solution like: Powershell Merge 2 lists Performance.

The method works as expected (also on other collections, any PowerShell version and .Net Core):

$Hash = @{Keys = 'MyKeys'; PSBase = 'MyPSBase'}
$Hash.get_Keys()
PSBase
Keys

The get_Keys() method is apparently derived from:

$Hash.get_Keys

OverloadDefinitions
-------------------
System.Collections.ICollection get_Keys()
System.Collections.ICollection IDictionary.get_Keys()

The point is that I can't find back where I found the referral to this get_Keys() method and there is hardly any documentation about this method.
Is it safe to use this method?

Spun answered 23/3, 2020 at 9:1 Comment(2)
Perhaps you can checkout the source code? I'm curious as well because I have never seen get_Keys().Stateroom
@Stateroom , sorry but I don't have enough C knowledge myself to checkout the source code...Spun
H
6

get_Keys() is indeed a valid (and recommended) way to access the Keys property of a dictionary without risking collision with a user-defined key.

The method works as expected (also on other collections, any PowerShell version and .Net Core):

Please beware that this only works on dictionaries ([hashtable], [ordered], [SortedList] etc.) - as it's inherited from the System.Collections.IDictionary interface.

The reason get_Keys() isn't listed in the public documentation is that it's intentionally hidden.

To understand why, we first need to understand the nature of properties in .NET

Properties in .NET

In .NET, data types can have different kinds of members. In the (C#) example below we define a class with two members, a field and a method:

class MyClass
{
    int MyField;

    int MyMethod(string n = "")
    {
        return int.Parse(n);
    }
}

The interesting thing to notice here is that the field acts like a variable - we can reference it to obtain the value of whatever integer is stored in MyField and we can assign a (new) value to it. The method, on the other hand, acts like a function - we can call it, including passing parameter values to it, and it can return a value.

But .NET has a third kind of member type that acts as a bit of a hybrid between a field and a method, and it looks a bit like this (in the case of a dictionary):

class MyDictionary
{
    string[] _keys;
    
    public string[] Keys
    {
        get
        {
            return _keys;
        }
        set
        {
            throw new InvalidOperationException("Don't mess with the keys!");
        }
    }
}

This is known as a property - from a user perspective, the Keys property will act just like a field - we can reference it to resolve its value and we can (attempt to) assign to it - but from an implementer's perspective we have a lot more control over it's behavior, like being able to (conditionally) throw an exception on assignment.

Now, when the above code is compiled, the C# compiler needs to store the get and set methods somewhere so that the CLR knows to execute them when someone tries to resolve the Keys member at runtime.

The convention is to generate them as regular class methods, named by prepending get_ and set_ to the property name in question. The compiler further marks these methods with the SpecialName attribute flag, allowing editors and analyzers to hide them in user interfaces and auto-completers - which is exactly why the method name doesn't automatically show up in intellisense and the likes.

Discovering property getters/setters

*In PowerShell classes, members are always either methods or properties, so in the following I'll use this PowerShell class definition for the examples:

class StackOverflowUser
{
  [string]$Name
  [int]$ID

  StackOverflowUser([string]$name, [int]$id)
  {
    $this.Name = $name
    $this.ID   = $ID
  }
}

$Mathias = [StackOverflowUser]::new("Mathias R. Jessen", 712649)

Using Get-Member

You can discover the automatic getters and setters associated with a property using Get-Member -Force:

PS C:\> $Mathias |Get-Member ?et_* -Force


   TypeName: StackOverflowUser

Name     MemberType Definition
----     ---------- ----------
get_ID   Method     int get_ID()
get_Name Method     string get_Name()
set_ID   Method     void set_ID(int )
set_Name Method     void set_Name(string )

Here we can see the getter and setter methods associated with $ID and $Name.

Using reflection

We can also find these directly from a [type] object:

PS C:\> $IDPropertyInfo = [StackOverflowUser].GetProperty("ID")
PS C:\> $IDPropertyInfo.GetMethod

Name                       : get_ID
DeclaringType              : StackOverflowUser
ReflectedType              : StackOverflowUser
MemberType                 : Method
MetadataToken              : 100663299
Module                     : RefEmit_InMemoryManifestModule
IsSecurityCritical         : True
IsSecuritySafeCritical     : False
IsSecurityTransparent      : False
MethodHandle               : System.RuntimeMethodHandle
Attributes                 : PrivateScope, Public, HideBySig, SpecialName
CallingConvention          : Standard, HasThis
ReturnType                 : System.Int32
ReturnTypeCustomAttributes : Int32
ReturnParameter            : Int32
IsCollectible              : True
IsGenericMethod            : False
IsGenericMethodDefinition  : False
ContainsGenericParameters  : False
MethodImplementationFlags  : Managed
IsAbstract                 : False
IsConstructor              : False
IsFinal                    : False
IsHideBySig                : True
IsSpecialName              : True
IsStatic                   : False
IsVirtual                  : False
IsAssembly                 : False
IsFamily                   : False
IsFamilyAndAssembly        : False
IsFamilyOrAssembly         : False
IsPrivate                  : False
IsPublic                   : True
IsConstructedGenericMethod : False
CustomAttributes           : {}

Notice that the getter above has the SpecialName attribute as discussed above

Note: output above is from PowerShell 7 and will be slightly different in Windows PowerShell due to changes in the reflection/type system APIs in .NET Core

I hope this explains :)

Hoyos answered 23/3, 2020 at 12:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.