クラスの拡張機能 - メソッドのラッピングとコマンド チェーン

Note

コミュニティの関心グループが Yammer から Microsoft Viva Engage に移行されました。 Viva Engage コミュニティに参加し、最新のディスカッションに参加するには、「 Finance and Operations Viva Engage Community へのアクセスを要求する 」フォームに入力し、参加するコミュニティを選択します。

クラス拡張またはクラス増強の機能性が改善されました。 拡張する基底クラスで定義したメソッドのロジックをラップできるようになりました。 イベント ハンドラーを使用せずに、パブリック メソッドと保護されたメソッドのロジックを拡張できます。 メソッドをラップするとき、基本クラスのパブリック メソッドと保護されたメソッド、および変数にもアクセスできます。 この方法で、トランザクションを開始し、クラスに関連付けられた状態変数を容易に管理することができます。

たとえば、モデルには次のコードが含まれています。

class BusinessLogic1
{
    str doSomething(int arg) 
    {
        // ...
    }
}

同じメソッド名を再利用することにより、拡張クラス内の doSomething メソッドの機能を強化することができるようになりました。 拡張クラスは強化されたクラスが定義されているモデルを参照するパッケージに属している必要があります。

[ExtensionOf(classStr(BusinessLogic1))]
final class BusinessLogic1_Extension
{
    str doSomething(int arg) 
    {
        // Part 1
        var s = next doSomething(arg + 4);
        // Part 2
        return s;
    }
}

この例では、doSomething のラッパーと next キーワードを必要に応じて使用することにより、メソッドのコマンド チェーン (CoC) を作成します。 CoC は、要求が一連の受信者によって処理される設計パターンです。 パターンは、送信者と受信者の疎結合をサポートします。

次のコードを実行します。

BusinessLogic1 object = new BusinessLogic1();
info(object.doSomething(33));

このコードを実行すると、 doSomething メソッドをラップするメソッドが見つかります。 システムは、BusinessLogic1_Extension クラスの doSomething メソッドなど、これらのメソッドの 1 つをランダムに実行します。 次の doSomething メソッドへの呼び出しが発生すると、システムは CoC 内の別のメソッドをランダムに選択します。 ラップされたメソッドがこれ以上ない場合は、システムは元の実装を呼び出します。

サポートされているバージョン

Important

この記事で説明されている機能 (CoC と保護されたメソッドと変数へのアクセス) は、プラットフォーム更新プログラム 9 で使用できます。 ただし、拡張するクラスは、プラットフォーム更新プログラム 9 以降でもコンパイルする必要があります。 2017 年 8 月の時点で、Finance and Operations 用アプリケーションのすべての現在のリリースは、プラットフォーム更新プログラム 8 以前でコンパイルされています。 そのため、基本パッケージ (Application Suite など) で定義されているメソッドをラップするには、プラットフォーム更新プログラム 9 以降でその基本パッケージを再コンパイルする必要があります。 例として、Application Suite モデルに存在するクラスを拡張する独自の拡張モデルを作成し、CoC を使用している場合、または保護されたメソッドと変数にアクセスする場合は、Application Suite と拡張機能モデルの両方を構築する必要があります。 また、ランタイム環境にこの機能をデプロイするには、両方のモデルを含む配置可能なパッケージを作成する必要があります。

機能

次のセクションでは、ラッピング メソッドと CoC メソッドの機能について詳しく説明します。

パブリック メソッドと保護対象メソッドのラッピング

拡張クラスを使用して、クラス、テーブル、データ エンティティ、またはフォームの保護されたメソッドまたはパブリック メソッドをラップできます。 ラッパー メソッドは、基本メソッドと同じシグネチャを持つ必要があります。

  • フォーム クラスを拡張する場合は、ルート レベルのメソッドのみをラップできます。 入れ子になったクラスで定義されているメソッドをラップすることはできません。
  • 現時点では、通常のクラスで定義されているメソッドのみをラップできます。 拡張クラスを拡張することにより、拡張クラスで定義されたメソッドをラップすることはできません。 この機能は、将来の更新で計画されています。

既定のパラメーターはどうなのですか ?

拡張クラスを使用して、既定のパラメーターを持つメソッドをラップできます。 ただし、ラッパー メソッドのメソッド シグネチャは、パラメータの既定値を含む必要がありません。

たとえば、次の簡単なクラスには、既定のパラメーターを持つメソッドがあります。

class Person 
{
    public void salute(str message = "Hi") {...}
}

この場合、ラッパー メソッドは次の例のようにする必要があります。

[ExtensionOf(classStr(Person))]
final class APerson_Extension
{
    public void salute(str message)
    {
        // ...
    }
}

APerson_Extension拡張クラスでは、salute メソッドにはメッセージ パラメーターの既定値は含まれません。

インスタンス メソッドと静的メソッドをラッピング

拡張クラスを使用して、インスタンスメソッドと静的メソッドをラップできます。 静的メソッドをラップする場合は、 static キーワードを使用して拡張内のメソッドを修飾する必要があります。

たとえば、次の A クラスについて考えてみます。

class A 
{
    public static void aStaticMethod(int parameter1)
    {
        // ...
    }
}

この場合、ラッパー メソッドは次の例のようにする必要があります。

[ExtensionOf(classStr(A)]
final class An_Extension
{
    public static void aStaticMethod(int parameter1)
    {
        // ...
        next aStaticMethod(parameter1);
    }
}

Important

静的メソッドをラップする機能はフォームには適用されません。 X++ では、フォーム クラスは新しいクラスではなく、通常のクラスとしてインスタンス化したり参照したりすることはできません。 フォーム内の静的メソッドにはセマンティクスがありません。

ラッパー メソッドは常に次を呼び出す必要があります

拡張クラス内のラッパー メソッドは常に を呼び出す必要があるため、チェーン内の次のメソッド、および最後には常に元の実装が呼び出されます。 この制限により、チェーン内のすべてのメソッドが結果に寄与することが保証されます。

この制限の現在の実装では、の呼び出しはメソッド本体の第 1 レベルのステートメントである必要があります。

いくつかの重要なルールを次に示します。

  • if ステートメント内での条件を呼び出すことはできません。
  • whiledo-while、または for ループ ステートメントで next を呼び出すことはできません。
  • return ステートメントをのステートメントの前に置くことはありません。
  • 論理式は最適化されているため、次への呼び出しは論理式では実行できません。 実行時に、完全な式の実行は保証されません。

Note

メソッドが置き換え可能な場合は、拡張担当者はコマンド チェーンを使用してメソッドをラップするときに無条件に next を呼び出す必要はありません。 エクステンダーはチェーンを壊すことができますが、限定的に中断するだけであることが求められます。 コンパイラは、属性 Replaceable を持つメソッドの next の呼び出しを強制しません。

派生クラスの拡張で基本メソッドをラッピング

次の例は、派生クラスの拡張機能で基準メソッドをラップする方法を示しています。 この例では、次のクラス階層を使用します。

class A
{
    public void salute(str message)
    {   
        info(message);
    }
}

class B extends A {}
class C extends A {}

階層には、1 つの基底クラス A があります。 BC の 2 つのクラスは、 A から派生します。次の図に示すように、派生クラスの 1 つ (この場合 は B) の拡張クラスを拡張または作成します。

[ExtensionOf(classStr(B))]
final class B_Extension
{
    public void salute(str message)
    {
        next salute(message);
        info("B extension");
    }
}

B_Extension クラスは B の拡張であり、B には salute メソッドのメソッド定義はありませんが、基底クラス A で定義されている salute メソッドをラップできます。したがって、B クラスのインスタンスにのみ、salute メソッドの折り返しが含まれます。 A クラスと C クラスのインスタンスは、B クラスの拡張で定義されているラッパー メソッドを呼び出すことはありません。

この 3 つのクラスを使用するメソッドを実装すると、この動作がより明確になります。

class ProgramTest 
{
    public static void main(Args args)
    {
        var a = new A();
        var b = new B();
        var c = new C();

        a.salute("Hi");
        b.salute("Hi");
        c.salute("Hi");
    }
}

a.salute("Hi")c.salute("Hi") の呼び出しの場合、Infolog には "Hi" というメッセージのみが表示されます。ただし、b.salute("Hi") が呼び出されると、Infolog に "Hi" と続けて "B 拡張子" が表示されます。

このメカニズムを使用することによって、特定の派生クラスに対してのみ元の方法をラップすることができます。

拡張クラスから保護されたメンバーへのアクセス

プラットフォーム更新プログラム 9 では、拡張クラスから保護されたメンバーにアクセスできます。 これらの保護されたメンバーには、フィールドとメソッドが含まれます。 このサポートは、ラップ メソッドに固有のものではなく、クラス拡張内のすべてのメソッドに適用されます。 したがって、クラス拡張は以前よりも強力です。

Hookable 属性

メソッドを明示的に [Hookable(false)] としてマークした場合、拡張クラスでメソッドをラップすることはできません。 次の例では、anyMethodAnyClass1 を補強するクラスでラップできません。

class AnyClass1 
{
    [Hookable(false)]
    public void anyMethod() {...}
}

Note

互換性の理由から、[Hookable(false)] は、前後のハンドラーに加えて、コマンド チェーンの動作を上書きします。 ただし、Hookable(true) は前処理ハンドラーと後処理ハンドラーにのみ適用され、コマンドのチェーンラッピングには影響しません。

最終的な方法および Wrappable 属性

拡張クラスで final としてマークするパブリック メソッドと保護されたメソッドをラップすることはできません。 この制限をオーバーライドするには、 Wrappable 属性を使用し、属性パラメーターを true ([Wrappable(true)]) に設定します。 (最終的でない) パブリック メソッドまたは保護されたメソッドの既定の機能をオーバーライドするには、それらのメソッドをラップ不可 ([Wrappable(false)]) としてマークします。

次の例では、doSomething メソッドは、パブリック メソッドである場合でもラップできないものとして明示的にマークされます。 doSomethingElse メソッドは、最後のメソッドであっても、折り返し可能と明示的にマークされます。

class AnyClass2 
{
    [Wrappable(false)]
    public void doSomething(str message) {...}

    [Wrappable(true)]
    final public void doSomethingElse(str message) {...}
}

データ ソース、データ フィールド、および制御といったフォームで入れ子になった概念の拡張

データ ソース、データ フィールド、コントロールなど、フォームの入れ子になった概念の CoC メソッドを実装するには、入れ子になった概念ごとに拡張クラスが必要です。

フォーム データ ソース

この例では、 FormToExtend はフォーム、 DataSource1 はフォーム内の有効な既存のデータ ソースであり、 initvalidateWrite はデータ ソースでラップできるメソッドです。

[ExtensionOf(formdatasourcestr(FormToExtend, DataSource1))]
final class FormDataSource1_Extension
{
    public void init()
    {
        next init();
        //...
        //use element.FormToExtendVariable to access form's variables and datasources
        //element.FormToExtendMethod() to call form methods
    }
 
    public boolean validateWrite()
    {
        boolean ret;
        //...
        ret = next validateWrite();
        //...
        return ret;
    }
}

フォーム データ フィールド

この例では、データ フィールドを拡張します。 FormToExtend はフォーム、 DataSource1 はフォームのデータ ソース、 Field1 はデータ ソースのフィールドであり、 検証 は、この入れ子になった概念でラップできる多くのメソッドの 1 つです。

[ExtensionOf(formdatafieldstr(FormToExtend, DataSource1, Field1))]
final class FormDataField1_Extension 
{ 
    public boolean validate()
    {
        boolean ret
        //...
        ret = next validate();
        //...
        return ret;
    }
}

コントロール

この例では、FormToExtend はフォーム、Button1 はフォーム内のボタンコントロール、clicked はそのボタンコントロールに関連付けてラッピングできるメソッドです。

[ExtensionOf(formControlStr(FormToExtend, Button1))]
final class FormButton1_Extension
{
    public void clicked()
    {
        next clicked();
        //...
    }
}

フォームで入れ子になった概念で CoC メソッドを書き込むときの要件と考慮事項

  • その他の CoC メソッドと同様、これらのメソッドは常に next を呼び出してチェーン内の次のメソッドを呼び出す必要があります。これにより、チェーンは実行時の動作でカーネルまたはネイティブ実装まで進むことができます。 next の呼び出しは、実行時の基本動作が常に期待どおりに実行されることを保証するために、フォーム自体からの super() の呼び出しと同じです。

  • 現在、Microsoft Visual Studio の X++ エディターでは、ラップできるメソッドの検出はサポートされていません。 したがって、折り返し対象となるメソッドとその正確なシグネチャを識別するための入れ子になった各概念については、システムのドキュメントを参照する必要があります。

  • 入れ子になったコントロール型の元の基本動作で定義されていないメソッドをラップする CoC を追加 することはできません 。 たとえば、拡張において methodInButton1 CoC を追加することはできません。 ただし、メソッドがパブリックまたは保護として定義されている場合は、コントロール拡張機能からこのメソッドを呼び出すことができます。 次に示すのは、 Button1 コントロールが FormToExtend フォームでメソッド InButton1 メソッドを持つような方法で定義されている例です。

    [Form]
    public class FormToExtend extends FormRun
    {
        [Control("Button")]
        class Button1
        {
            public void methodInButton1(str param1)
            {
                info("Hi from methodInButton1");
                //...
    
  • 拡張機能からそのフォームの入れ子になった概念に対する CoC メソッドをサポートするために元のフォームが定義されているモジュールを再コンパイルする必要 はありません 。 たとえば、前の例の FormToExtend フォームが ApplicationSuite モジュールにある場合、異なるモジュールからそのフォームで入れ子になった概念の CoC で拡張するために ApplicationSuite を再コンパイルする必要はありません。

テーブルとデータ エンティティの拡張機能

概念ごとに拡張クラスが必要です。

テーブル

この例では、 TableToExtend はテーブルであり、 deletecanSubmitToWorkflowキャプション はテーブルでラップできるメソッドです。

[ExtensionOf(tablestr(TableToExtend))]
final class TableToExtend_Extension
{
    public void delete()
    {
        next delete();
        //...
    }
 
     public boolean canSubmitToWorkflow(str _workflowType)
    {
        boolean ret;
        //...
        ret = next canSubmitToWorkflow(_workflowType);
        //...
        return ret;
    }
        
    public str caption()
    {
        str ret;
        //...
        ret = next caption();
        //...
        return ret;
    }
}

データ エンティティ

この例では、 DataEntityToExtend はデータ エンティティであり、 validateDeletevalidateWrite はデータ エンティティでラップできるメソッドです。

[ExtensionOf(tableStr(DataEntityToExtend))]
final class DataEntityToExtend_Extension
{
    public boolean validateDelete()
    {
        boolean ret;
        //...
        ret = next validateDelete();
        //...
        return ret;
    }

    public boolean validateWrite()
    {
        boolean ret;
        //...
        ret = next validateWrite();
        //...
        return ret;
    }
}

ラッパー メソッドに関する制限事項

次のセクションでは、CoC およびメソッド ラッピングの使用上の制限について説明します。

プラットフォーム更新プログラム 8 以前を使用してシステムがコンパイルする X++ クラス

メソッド ラッピング機能には、X++ コンパイラがプラットフォーム更新プログラム 9 以降の一部として出力する特定の機能が必要です。 以前のバージョンでコンパイルされるメソッドには、この機能をサポートするインフラストラクチャがありません。

プラットフォーム更新 16 以降ではフォーム内で入れ子になったタイプのメソッドをラップできます。

プラットフォーム更新プログラム 16 では、クラス拡張を使用して、フォーム (データ ソースとコントロール) 内に入れ子になった型にメソッドをラップする機能が追加されました。 この機能は、コマンドチェーンがデータ ソース メソッドとフォーム コントロール メソッドのオーバーライドを提供できることを意味します。

ただし、入れ子になった型 (フォーム コントロールとフォーム データ ソース) に対する純粋な X++ メソッドの折り返し (拡張) は、他の型 (フォーム、テーブル、データ エンティティ) と同様に、まだサポートされていません。 現時点では、開発者がフォーム内の型に対して純粋に X++ メソッドに対して Chain of Command を使用する場合、コードはコンパイルされますが、拡張メソッドは実行時に呼び出されません。 この機能は、将来の更新で計画されています。

テーブルとデータ エンティティの実装されていないシステム メソッドは、プラットフォーム更新 22 以降でラップできます。

プラットフォーム更新プログラム 16 では、クラス拡張を使用して入れ子になったクラスでメソッドをラップする機能が導入されました。 X++ の入れ子になったクラスの概念は、データソース メソッドやフォームコ ントロール メソッドを上書きするためのフォームに適用されます。

プラットフォーム更新 21 以降では、次の呼び出しを try/catch/finally 内に配置できます

CoC 拡張メソッドでは、次の呼び出しを条件付きで呼び出さないでください。 ただし、プラットフォーム更新プログラム 21 以降では、try/catch/finally 内で次の呼び出しを行って、例外の標準的な処理とリソースのクリーンアップを可能にすることができます。

    public void someMethod()
    {
        try
        {
            //...
            next someMethod();
            //...
        }
        catch(Exception::Error)
        {
            //...
        }
    }

コンストラクターの拡張機能

コンストラクターを拡張することはできません。 拡張クラスで定義する 新しい メソッドは、拡張クラス自体のコンストラクターを定義します。 新しいメソッドをパブリックにする必要があり、引数を含めることはできません。 詳細については、コンストラクター を参照してください。

ツール

この記事で説明する機能については、Microsoft Visual Studio X++ エディターでは、相互参照と Microsoft IntelliSense の完全なサポートはまだ提供されていません。