Pseudo-Polymorphism in PowerShell

When learning a strongly-typed, object oriented language one of the first concepts you learn is polymorphism. Polymorphic members accept object types that define a particular contract. Class instances that meet this contract, even if they extend from it, can be provided to these members. In PowerShell, this can be true when dealing with .NET classes. For example, our good friend WebClient exposes some polymorphic methods. When you invoke GetWebRequest, it returns a WebRequest class instance. What you’ll notice, via MSDN, is that there are actually more specific versions of a WebRequest. For example, the HttpRequest that is returned when using the WebClient and the HTTP protocol.

hier

 

The GetWebResponse method accepts a WebRequest rather than any one of the inheritors of the class. This makes it polymorphic.

When using objects as arguments to PowerShell functions, the same polymorphism can be used for .NET types that are already defined but not for objects that are built upon PSCustomObject. Here is how to enforce simple polymorphism in PowerShell functions.

I’ve created a New-Interface function. It is used to define the contract that our new PSCustom objects will be be required to meet.

function New-Interface
{
    param(
    [String]$Name,
    [PSCustomObject]$Definition
    )
 
    $InterfaceType = Add-Type -TypeDefinition "
        public class $Name : System.Management.Automation.PSObject
        {
            public $Name(System.Management.Automation.PSObject obj) : base(obj)
            {
                if (Definition == null) throw new System.NullReferenceException(`"Defintion is null!`");
                if (obj == null) throw new System.ArgumentNullException(`"Obj was null!`");
 
                foreach(var interfaceMember in Definition.Members)
                {
                    if (interfaceMember == null) continue;
 
                    bool found = false;
                    foreach(var inheritorMember in obj.Members)
                    {
                        if (inheritorMember == null) continue;
 
                        if(inheritorMember.IsInstance &&
                           inheritorMember.MemberType == interfaceMember.MemberType &&
                           inheritorMember.Name == interfaceMember.Name)
                        {
                            found = true;
                            break;
                        }
                    }
 
                    if (!found)
                    {
                        throw new System.InvalidCastException(`"The object is not of type $Name. Missing member `" + interfaceMember.Name);
                    }
                }
            }
 
            public static System.Management.Automation.PSObject Definition {get;set;}
        }
    " -PassThru
    $acceleratorsType = [PSCustomObject].Assembly.gettype("System.Management.Automation.TypeAccelerators")
    $acceleratorsType::Add($Name, $InterfaceType);
    $InterfaceType::Definition = $Definition
}

Since PowerShell can convert between objects based on constructor, the constructor for the new interface type accepts a PSObject. This means that we will be able to directly cast from a PSCustomObject into this particular type. The object also inherits from PSObject so that we can maintain any members that have been defined on the object that we are using to construct an instance of this class. The body of the constructor validates that the input PSObject contains all the correct members. In this case it does not validate Method signatures but could easily be updated to do so. Finally, a TypeAccelerator is used to make casting to this object snappy.

Next, we create our new interface. I’ll create the most widely used polymorphism example: the IAnimal. Implementors of IAnimal will be required to return a Type and expose a Draw property. Both need to be strings.

New-Interface -Definition ([PSCustomObject]@{Type=[string];Draw=[string]}) -Name IAnimal

Now, we can define a polymorphic function that accepts IAnimal. Pretty simple. Since animals are guarenteed to have Draw properties, we can safely call it from within the method.

function Out-Animal([IAnimal]$animal)
{
    $animal.Draw
}

Let’s have some fun and put some animals together. I grabbed these ACSII art drawings from Chris.com. We are creating a PSCustomObject just like we would do with any old custom object. The final object we create is a toaster and is not a valid animal because it does not expose the Draw property. ;)

 
$Bear = [PSCustomObject]@{Type="Bear";Draw="
     :`"'._..---.._.'`";
     `.             .'
     .'    ^   ^    `.
    :      a   a      :                 __....._
    :     _.-0-._     :---'`"`"'`"-....--'`"        '.
     :  .'   :   `.  :                          `,`.
      `.: '--'--' :.'                             ; ;
       : `._`-'_.'                                ;.'
       `.   '`"'                                   ;
        `.               '                        ;
         `.     `        :           `            ;
          .`.    ;       ;           :           ;
        .'    `-.'      ;            :          ;`.
    __.'      .'      .'              :        ;   `.
  .'      __.'      .'`--..__      _._.'      ;      ;
  `......'        .'         `'""'`.'        ;......-'
 jgs    `.......-'                 `........'
";}
 
$Cow = [PSCustomObject]@{Type="Cow";Draw="
                                      __.----.___
           ||            ||  (\(__)/)-'||      ;--` ||
          _||____________||___`(QQ)'___||______;____||_
          -||------------||----)  (----||-----------||-
          _||____________||___(o  o)___||______;____||_
          -||------------||----`--'----||-----------||-
           ||            ||       `|| ||| || ||     ||jgs
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
";}
$Pig = [PSCustomObject]@{Type="Pig";Draw="
 
            (\____/)
            / @__@ \    
           (  (oo)  )   
            `-.~~.-'
             /    \             
           @/      \_          
          (/ /    \ \)
      jgs  WW`----'WW
 
";}
$Toaster= [PSCustomObject]@{Type="Toaster";}

Finally, we can call our polymorphic function and pass in each one of the animals.

 
Out-Animal $Bear
Out-Animal $Cow
Out-Animal $Pig
Out-Animal $Toaster

The resulting output looks something like this.


     :"'._..---.._.'";
     .             .'
     .'    ^   ^    .
    :      a   a      :                 __....._
    :     _.-0-._     :---'""'"-....--'"        '.
     :  .'   :   .  :                          ,.
      .: '--'--' :.'                             ; ;
       : ._-'_.'                                ;.'
       .   '"'                                   ;
        .               '                        ;
         .             :                       ;
          ..    ;       ;           :           ;
        .'    -.'      ;            :          ;.
    __.'      .'      .'              :        ;   .
  .'      __.'      .'--..__      _._.'      ;      ;
  ......'        .'         '"'.'        ;......-'
 jgs    .......-'                 ........'


                                      __.----.___
           ||            ||  (\(__)/)-'||      ;-- ||
          _||____________||___(QQ)'___||______;____||_
          -||------------||----)  (----||-----------||-
          _||____________||___(o  o)___||______;____||_
          -||------------||------'----||-----------||-
           ||            ||       || ||| || ||     ||jgs
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^



            (\____/)
            / @__@ \    
           (  (oo)  )   
            -.~~.-'
             /    \             
           @/      \_          
          (/ /    \ \)
      jgs  WW----'WW


Out-Animal : Cannot process argument transformation on parameter 'animal'.
Cannot convert value "@{Type=Toaster}" to type "IAnimal". 
Error: "The object is not of type IAnimal. Missing member Draw"
At C:\Users\Adam\Documents\My Dropbox\PowerShell\New-Interface.ps1:103 char:12
+ Out-Animal $Toaster
+            ~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Out-Animal], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Out-Animal

You can leave a response, or trackback from your own site.

5 Responses to “Pseudo-Polymorphism in PowerShell”

  1. [...] Pseudo-Polymorphism in PowerShell (Adam Driscoll) [...]

  2. This Webpage says:

    Hey there! This post could not be written any better!

    Reading through this post reminds me of my good old room mate!
    He always kept talking about this. I will forward this write-up to him.

    Pretty sure he will have a good read. Thanks for sharing!

Leave a Reply

In an effort to prevent automatic filling, you should perform a task displayed below.



2 + eight =