reintroduce
Delphi でキーワードを使用する動機は何ですか?
親クラスの仮想関数と同じ名前の関数を含む子クラスがあり、オーバーライド修飾子で宣言されていない場合、コンパイル エラーになります。このような状況で reintroduce 修飾子を追加すると、エラーは修正されますが、コンパイル エラーの理由を把握したことはありません。
reintroduce
Delphi でキーワードを使用する動機は何ですか?
親クラスの仮想関数と同じ名前の関数を含む子クラスがあり、オーバーライド修飾子で宣言されていない場合、コンパイル エラーになります。このような状況で reintroduce 修飾子を追加すると、エラーは修正されますが、コンパイル エラーの理由を把握したことはありません。
祖先クラスのメソッドと同じ名前を持つ子孫クラスでメソッドを宣言すると、その祖先メソッドが隠されます。つまり、その子孫クラスのインスタンス (そのクラスとして参照される) がある場合は、祖先の振る舞いを取得しません。先祖のメソッドが仮想または動的である場合、コンパイラは警告を出します。
この警告メッセージを抑制するには、次の 2 つの選択肢のいずれかがあります。
したがって、オーバーライドと再導入の違いはポリモーフィズムにあります。reintroduceを使用して、子孫オブジェクトを親型としてキャストすると、そのメソッドを呼び出すと祖先メソッドが取得されますが、子孫型にアクセスすると、子孫の動作が取得されます。オーバーライドを使用すると、常に子孫を取得できます。祖先メソッドがvirtualでもdynamicでもなかった場合、その動作は暗黙的であるため、reintroduceは適用されません。(実際にはクラス ヘルパーを使用できますが、ここでは説明しません。)
Malach が言ったことにもかかわらず、親がvirtualでもdynamicでもない場合でも、再導入されたメソッドでinheritedを呼び出すことができます。
reintroduce は本質的にoverrideと同じですが、非動的および非仮想メソッドで機能し、オブジェクト インスタンスが祖先型の式を介してアクセスされる場合、動作を置き換えません。
さらなる説明:
再導入は、エラーを犯していないという意図をコンパイラに伝える方法です。overrideキーワードを使用して祖先のメソッドをオーバーライドしますが、祖先メソッドがvirtualまたはdynamicである必要があり、オブジェクトが祖先クラスとしてアクセスされたときに動作を変更する必要があります。reintroduceと入力します。仮想または動的祖先メソッドと同じ名前のメソッドを誤って作成していないことをコンパイラーに伝えることができます (コンパイラーが警告を出さなかったら迷惑です)。
ここには、メンバー関数をサイレントに非表示にできるコンパイラが悪い考えである理由について、多くの回答があります。しかし、メンバー関数を黙って隠す最新のコンパイラはありません。そうすることが許可されている C++ でさえ、それについて常に警告があり、それで十分なはずです。
では、なぜ「再導入」が必要なのでしょうか? 主な理由は、これは、コンパイラの警告をもう見ていなくても、実際に偶発的に現れる可能性がある一種のバグだからです。たとえば、TComponent から継承していて、Delphi デザイナーが新しい仮想関数を TComponent に追加するとします。悪いニュースは、5 年前に作成して他のユーザーに配布した派生コンポーネントに、その名前の関数が既に含まれていることです。
コンパイラがその状況を受け入れた場合、一部のエンド ユーザーはコンポーネントを再コンパイルする可能性がありますが、警告は無視してください。奇妙なことが起こり、あなたは非難されるでしょう。これには、関数が同じ関数ではないことを明示的に受け入れる必要があります。
まず第一に、「再導入」は継承チェーンを壊すため、使用すべきではありません。つまり、決して使用しないでください。私は Delphi で働いていた間 (約 10 年間)、このキーワードを使用している多くの場所に出くわしましたが、それは常に設計上の誤りでした。
それを念頭に置いて、これが機能する最も簡単な方法は次のとおりです。
私が言ったように、それは純粋な悪であり、絶対に避けなければなりません (まあ、少なくともそれは私の意見です)。それはgotoを使うようなものです- ただひどいスタイルです:D
RTL は reintroduce を使用して、継承されたコンストラクターを非表示にします。たとえば、TComponent には 1 つの引数を取るコンストラクタがあります。ただし、TObject にはパラメータのないコンストラクタがあります。RTL は、新しい TComponent をインスタンス化するときに、TObject から継承されたパラメータなしのコンストラクタではなく、TComponent の引数が 1 つのコンストラクタのみを使用することを望んでいます。そのため、 reintroduce を使用して、継承されたコンストラクターを非表示にします。このように、再導入は、C# でパラメーターなしのコンストラクターをプライベートとして宣言することに少し似ています。
tl;dr:非仮想メソッドをオーバーライドしようとしても意味がありません。キーワードreintroduce
を追加して、間違いを犯していることを認めます。
reintroduce 修飾子の目的は、一般的な論理エラーを防ぐことです。
reintroduce キーワードが警告をどのように修正するかは一般的な知識であると仮定し、警告が生成される理由と、キーワードが言語に含まれる理由を説明します。以下の Delphi コードを検討してください。
TParent = Class
Public
Procedure Procedure1(I : Integer); Virtual;
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Virtual;
End;
TChild = Class(TParent)
Public
Procedure Procedure1(I : Integer);
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Override;
Procedure Setup(I : Integer);
End;
procedure TParent.Procedure1(I: Integer);
begin
WriteLn('TParent.Procedure1');
end;
procedure TParent.Procedure2(I: Integer);
begin
WriteLn('TParent.Procedure2');
end;
procedure TChild.Procedure1(I: Integer);
begin
WriteLn('TChild.Procedure1');
end;
procedure TChild.Procedure2(I: Integer);
begin
WriteLn('TChild.Procedure2');
end;
procedure TChild.Setup(I : Integer);
begin
WriteLn('TChild.Setup');
end;
Procedure Test;
Var
Child : TChild;
Parent : TParent;
Begin
Child := TChild.Create;
Child.Procedure1(1); // outputs TChild.Procedure1
Child.Procedure2(1); // outputs TChild.Procedure2
Parent := Child;
Parent.Procedure1(1); // outputs TParent.Procedure1
Parent.Procedure2(1); // outputs TParent.Procedure2
End;
上記のコードでは、TParent の両方のプロシージャが隠されています。それらが非表示であるということは、TChild ポインターを介してプロシージャーを呼び出すことができないことを意味します。コード サンプルをコンパイルすると、1 つの警告が生成されます。
[DCC 警告] Project9.dpr(19): W1010 メソッド 'Procedure1' は、基本型 'TParent' の仮想メソッドを非表示にします
仮想機能の警告のみで、他の機能の警告が表示されないのはなぜですか? どちらも隠れています。
Delphi の利点は、ライブラリの設計者が既存のクライアント コードのロジックを壊すことを恐れずに、新しいバージョンをリリースできることです。これは、ライブラリ内の親クラスに新しい関数を追加することが危険に満ちている Java とは対照的です。これは、クラスが暗黙的に仮想化されているためです。上記の TParent がサードパーティのライブラリに存在し、ライブラリの製造元が以下の新しいバージョンをリリースするとします。
// version 2.0
TParent = Class
Public
Procedure Procedure1(I : Integer); Virtual;
Procedure Procedure2(I : Integer);
Procedure Procedure3(I : Integer); Virtual;
Procedure Setup(I : Integer); Virtual;
End;
procedure TParent.Setup(I: Integer);
begin
// important code
end;
クライアントコードに次のコードがあると想像してください
Procedure TestClient;
Var
Child : TChild;
Begin
Child := TChild.Create;
Child.Setup;
End;
クライアントにとって、コードがライブラリのバージョン 2 または 1 に対してコンパイルされているかどうかは問題ではありません。どちらの場合でも、ユーザーの意図どおりに TChild.Setup が呼び出されます。そして図書館で。
// library version 2.0
Procedure TestLibrary(Parent : TParent);
Begin
Parent.Setup;
End;
TChild パラメータを指定して TestLibrary を呼び出すと、すべてが意図したとおりに機能します。ライブラリの設計者は TChild.Setup についての知識がなく、Delphi ではこれが害を及ぼすことはありません。上記の呼び出しは TParent.Setup に正しく解決されます。
Java で同等の状況では何が起こるでしょうか? TestClient は意図したとおりに正しく動作します。TestLibrary はそうしません。Java では、すべての関数が仮想であると想定されています。Parent.Setup は TChild.Setup に解決されますが、TChild.Setup が作成されたとき、彼らは将来の TParent.Setup について何も知らなかったことを思い出してください。そのため、ライブラリの設計者が TParent.Setup を呼び出すことを意図していた場合、彼らが何をしても、それは呼び出されません。そして確かに、これは壊滅的なものになる可能性があります。
そのため、Delphi のオブジェクト モデルでは、子クラスのチェーンに沿って仮想関数を明示的に宣言する必要があります。これの副作用は、子メソッドにオーバーライド修飾子を追加するのを忘れやすいことです。Reintroduce キーワードの存在は、プログラマにとって便利です。Delphi は、警告を生成することによって、プログラマが穏やかに説得され、そのような状況で意図を明示的に述べるように設計されています。
Reintroduce は、このメソッドで定義されたコードを、このクラスとその子孫のエントリ ポイントとして呼び出す必要があることをコンパイラに伝えます。祖先のチェーンに同じ名前の他のメソッドがあるかどうかは関係ありません。
を作成するTDescendant.MyMethod
と、TDescendants が同じ名前の別のメソッドを追加する際に混乱が生じる可能性があり、コンパイラが警告します。
再導入はそれを明確にし、使用するコンパイラを知っていることをコンパイラに伝えます。
ADescendant.MyMethod
TDescendant を(ADescendant as TAncestor).MyMethod
呼び出し、TAncestor を呼び出します。いつも!迷うな…。コンパイラハッピー!
これは、子孫メソッドを仮想にするかどうかに関係なく当てはまります。どちらの場合も、仮想チェーンの自然なつながりを壊したいのです。また、新しいメソッド内から継承されたコードを呼び出すことを妨げません。
祖先クラスにも同じ名前のメソッドがあり、それが必ずしも virtual であると宣言されていない場合、(このメソッドを非表示にするため) コンパイラの警告が表示されます。
言い換えれば、祖先関数を隠してこの新しい関数に置き換えることを知っていることをコンパイラに伝え、意図的にそうします。
そして、なぜあなたはこれをするのですか?メソッドが親クラスで仮想である場合、唯一の理由はポリモーフィズムを防ぐことです。それ以外はオーバーライドするだけで、継承を呼び出さないでください。ただし、親メソッドが virtual として宣言されていない場合 (コードを所有していないなどの理由で、それを変更することはできません)、そのクラスから継承し、コンパイラの警告を表示せずにクラスから継承させることができます。
まず、上で述べたように、仮想メソッドを故意に再導入してはいけません。reintroduce の唯一の適切な使用法は、祖先の作成者 (あなたではない) が、子孫と競合するメソッドを追加し、子孫メソッドの名前を変更することができない場合です。第 2 に、別のパラメーターを使用して再導入したクラスでも、元のバージョンの仮想メソッドを簡単に呼び出すことができます。
type
tMyFooClass = class of tMyFoo;
tMyFoo = class
constructor Create; virtual;
end;
tMyFooDescendant = class(tMyFoo)
constructor Create(a: Integer); reintroduce;
end;
procedure .......
var
tmp: tMyFooClass;
begin
// Create tMyFooDescendant instance one way
tmp := tMyFooDescendant;
with tmp.Create do // please note no a: integer argument needed here
try
{ do something }
finally
free;
end;
// Create tMyFooDescendant instance the other way
with tMyFooDescendant.Create(20) do // a: integer argument IS needed here
try
{ do something }
finally
free;
end;
では、読みにくくする以外に、仮想メソッドを再導入する目的は何でしょうか?
これは、フレームワークのバージョン (VCL を含む) のために言語に導入されました。
既存のコード ベースがあり、フレームワークの更新により(たとえば、新しい Delphi バージョンを購入したため)、コード ベースの先祖のメソッドと同じ名前の仮想メソッドが導入された場合reintroduce
、W1010 警告を取り除きます。
これは、 を使用する唯一の場所ですreintroduce
。
reintroduceを使用すると、先祖と同じ名前のメソッドを宣言できますが、パラメーターは異なります。バグや間違いとは何の関係もありません!!!
たとえば、コンストラクターによく使用します...
constructor Create (AOwner : TComponent; AParent : TComponent); reintroduce;
これにより、ツールバーやカレンダーなどの複雑なコントロールに対して、よりクリーンな方法で内部クラスを作成できます。私は通常、それよりも多くのパラメータを持っています。いくつかのパラメーターを渡さずにクラスを作成することは、ほとんど不可能であるか、非常に面倒な場合があります。
ビジュアル コントロールの場合、Application.Processmessages は Create の後に呼び出される可能性があり、これらのパラメーターを使用するには遅すぎる可能性があります。
constructor TClassname.Create (AOwner : TComponent; AParent : TComponent);
begin
inherited Create (AOwner);
Parent := AParent;
..
end;