しばたテックブログ

気分で書いている技術ブログです。

AutomationNullとは何なのか?

ちょっと前のはなしなのですがこちらのGistを読みました。

こちらのGistではPowerShellを使う上でハマりがちな罠について詳しく記述されておりPowerShellに対する非常に良い批判となっています。

で、このGistの中で$nullAutomationNullの2つのNULLについて触れられています。
私自身AutomationNullの存在は知っていたのですが表に露出しない内部的なものだと認識しており、これまで全然気にかけていませんでした。

本エントリではこのAutomationNullについて分かる範囲で説明したいと思います。

お断り

本エントリの内容はあくまで私個人の調査結果でありPowerShellとして公式な何かを表明するものではありません。

極力間違いの無い様に調べた結果を書いていますが内容に誤りがある可能性は多分にあります。
その点は割り引いてご覧ください。

あと本エントリに対するフィードバックは大歓迎です。
おかしな点などありましたら是非お知らせください。

$nullとは何なのか

現在PowerShellはオープンソースとなっています。
$nullAutomationNullの2つのNULLについて知るにはその実装を見てみるのが一番手っ取り早いでしょう。

最初に$nullについてその実装を見てみます。
PowerShellにおいてNULLを表現する変数である$nullSystem.Management.Automation.NullVariableに定義されています。

/// ShellVariable.cs(Ver.6.0.1) より引用

/// <summary>
/// This class is used for $null.  It always returns null as a value and accepts
/// any value when it is set and throws it away.
/// </summary>
///
internal class NullVariable : PSVariable
{
    /// <summary>
    /// Constructor that calls the base class constructor with name "null" and
    /// value null.
    /// </summary>
    ///
    internal NullVariable() : base(StringLiterals.Null, null, ScopedItemOptions.Constant | ScopedItemOptions.AllScope)
    {
    }

    /// <summary>
    /// Always returns null from get, and always accepts
    /// but ignores the value on set.
    /// </summary>
    public override object Value
    {
        get
        {
            return null;
        }

        set
        {
            // All values are just ignored
        }
    }

    /// <summary>
    /// Gets the description for $null.
    /// </summary>
    public override string Description
    {
        get { return _description ?? (_description = SessionStateStrings.DollarNullDescription); }
        set { /* Do nothing */ }
    }
    private string _description;

    /// <summary>
    /// Gets the scope options for $null which is always None.
    /// </summary>
    public override ScopedItemOptions Options
    {
        get { return ScopedItemOptions.None; }
        set { /* Do nothing */ }
    }
}

Valueプロパティが常にnullを返し、入力はすべて破棄される様になっています。
$nullはPowerShellにおいて.NET Frameworkのnullを表現するものであることがわかります。

ちなみにこの変数のDescriptionには

PS C:\> (Get-Variable 'null').Description
References to the null variable always return the null value. Assignments have no effect.

References to the null variable always return the null value. Assignments have no effect.

と記述されています。

AutomationNullとは何なのか?

そしてAutomationNullSystem.Management.Automation.AutomatinNullに定義されています。

/// AutomationNull.cs(Ver.6.0.1) より引用

/// <summary>
/// This is a singleton object that is used to indicate a void return result.
/// </summary>
/// <remarks>
/// It's a singleton class. Sealed to prevent subclassing. Any operation that
/// returns no actual value should return this object AutomationNull.Value.
/// Anything that evaluates an MSH expression should be prepared to deal
/// with receiving this result and discarding it. When received in an
/// evaluation where a value is required, it should be replaced with null.
/// </remarks>
public static class AutomationNull
{
    #region private_members

    // Private member for Value.

    #endregion private_members

    #region public_property

    /// <summary>
    /// Returns the singleton instance of this object.
    /// </summary>
    public static PSObject Value { get; } = new PSObject();

    #endregion public_property
}

ソースを見る限りいわゆるNULLオブジェクトとして定義されており非常にシンプルな内容です。
実装から見えるものはほとんど無いですが、summaryとremarksタグのコメントに非常に重要なことが書かれています。

AutomationNullはPowerShellにおけるVoidである

summaryコメントにAutomationNullの用途がズバリ書き記されています。

This is a singleton object that is used to indicate a void return result.

indicate a void return resultとある様にAutomationNullはPowerShellにおける式や文を評価した際に戻り値がないことを表すクラスであり、いわゆるVoid型と言われるものに相当します。

そしてremarksコメントに

Any operation that returns no actual value should return this object AutomationNull.Value.
Anything that evaluates an MSH expression should be prepared to deal
with receiving this result and discarding it. When received in an
evaluation where a value is required, it should be replaced with null.

とあり、AutomationNull.Valueを受け取った文や式はその値を切り捨てるべきとされ、値が必要とされる場合はnull(=$null)に置き換えられる様に要請されています。


AutomationNullがVoidであるわかりやすい例として、[void]型(System.Void型)へのキャストはAutomationNull.Valueを返す実装となっている点が挙げられます。

/// LanguagePrimitives.cs(Ver.6.0.1) より一部引用

    public abstract class PSTypeConverter
    {
        // ・・・ (省略) ・・・

        //
        // [void]型へのキャストは AutomationNull.Value を返す様になっている
        //
        private static object ConvertToVoid(object valueToConvert,
                                            Type resultType,
                                            bool recursion,
                                            PSObject originalValueToConvert,
                                            IFormatProvider formatProvider,
                                            TypeTable backupTable)
        {
            typeConversion.WriteLine("returning AutomationNull.Value.");
            return AutomationNull.Value;
        }

        // ・・・ (省略) ・・・

        private static ConversionData FigureLanguageConversion(Type fromType, Type toType,
                                                               out PSConverter<object> valueDependentConversion,
                                                               out ConversionRank valueDependentRank)
        {

            // ・・・ (省略) ・・・

            if (toType == typeof(void))
            {
                return CacheConversion<object>(fromType, toType, LanguagePrimitives.ConvertToVoid, ConversionRank.Language);
            }

            // ・・・ (省略) ・・・
        }

        // ・・・ (省略) ・・・
    }

このほかにも、その実装までは紹介できませんが、AutomationNullはパイプラインに渡されず破棄される、ストリームの出力も無視されるなどの挙動が確認できており、これがAutomationNullがVoidであることの裏付けになっていると私は考えています。

$nullとAutomationNullの違い

ここまででPowerShellに2つのNULLである$nullAutomationNullが存在している理由については明らかにできたかと思います。

これまで私はAutomationNullについてはあくまで内部用で表には露出しないと思っていたのですが、実態は先のGistにある様に当たり前の様に露出します。
(できるだけユーザーに対して見えない様にしていると感じますが、見えないけど確実に存在する状態が現状と思えます)

それどころか、何気なくPowerShellを使い$nullだと思っているもののほとんどが実はAutomationNull.Valueでした。

これは私の予想ですが、PowerShellにおけるNULLはAutomationNullが基本であり、$nullは.NET Frameworkの機能を呼び出すとき等でNULL値を明示する必要がある際に使う特殊な値と認識するのが正解の様です。

ただ、AutomationNullはユーザーに対して見えない様にしたいがために全くドキュメント化されておらず$nullと同じに見える挙動さえします。

この様な現状に対しては正直批判も止む無しと思いますが、直ちにどうにかすることができないのも現実なので割り切りは必要です。

$nullとAutomationNullの挙動の違い

ここから$nullAutomationNullの挙動の違いについて触れます。

原則として、

  • AutomationNullはVoidであり、評価を打ち切るものである
  • $nullは.NET Frameworkのnullを明示するものである

という役割の違いが挙動の違いに表れています。

先述のGistの例を出すと、

# NULL値を明示している
> @($null).Length
1

これはNULL値を明示的に利用する必要があるため要素数1の配列となります。

# AutomationNull.Valueの評価は打ち切られる
> @([System.Management.Automation.Internal.AutomationNull]::Value).Length
0

こちらは配列部分式演算子(@())をAutomationNull.Valueに対して評価することになり、評価を打ち切る必要があるため空の配列となります。

こちらの記述だと違和感があるかもしれませんが、以下の様な記述であれば0を返すことにさほど違和感を感じないのではないかと思います。

# [void]キャストはAutomationNull.Valueを返す
> @([void](Get-Date)).Length
0

補足資料

本エントリでの説明はこれくらいにしておきたいと思います。

AutomationNullの挙動についてはGitHubのIssueにもいくつか挙げられおり、その是非について広く議論されています。
最後にいくつかIssueを紹介しますので興味があればぜひご覧ください。

github.com

github.com

github.com