たとえば、次のようなフラグメントを作成するコードをいくつか見ました。
Fragment myFragment=new MyFragment();
これは、変数をMyFragmentではなくFragmentとして宣言します。MyFragmentはFragmentの子クラスです。このコードは次のようになるはずなので、このコード行は満足していません。
MyFragment myFragment=new MyFragment();
より具体的には、それは本当ですか?
または質問の一般化で、使用するのは悪い習慣ですか?
Parent x=new Child();
の代わりに
Child x=new Child();
コンパイルエラーなしで前者を後者に変更できるとしたら?
それはコンテキストに依存しますが、可能な限りmost abstractタイプを宣言する必要があると私は主張します。そうすれば、コードはできるだけ一般的になり、無関係な詳細に依存しなくなります。
例として、LinkedList
とArrayList
があり、どちらもList
から派生しているとします。コードがどのような種類のリストでも同じように機能する場合、サブクラスの1つに任意に制限する理由はありません。
JacquesBの答えは正しいですが、少し抽象的です。いくつかの側面をより明確に強調しましょう:
コードスニペットでは、明示的にnew
キーワードを使用します。具体的な型に固有である可能性が高い追加の初期化手順を続行したい場合があります。次に、その特定の具体的な型で変数を宣言する必要があります。
あなたがそのアイテムをどこか別の場所から入手した場合、状況は異なります。 IFragment fragment = SomeFactory.GiveMeFragments(...);
。ここで、ファクトリは通常、可能な限り最も抽象的な型を返す必要があり、下位のコードは実装の詳細に依存するべきではありません。
パフォーマンスが異なる可能性があります。
派生クラスがシールされている場合、コンパイラーはインライン化して最適化するか、そうでなければ仮想呼び出しになるものを排除できます。したがって、パフォーマンスに大きな違いが生じる可能性があります。
例:ToString
は仮想呼び出しですが、String
はシールされています。 String
では、ToString
は何もしないので、object
として宣言した場合、これは仮想呼び出しです。String
を宣言した場合、コンパイラはクラスがシールされているため、派生クラスがメソッドをオーバーライドしているため、何もできません。 ArrayList
とLinkedList
にも同様の考慮事項が適用されます。
したがって、オブジェクトの具体的なタイプがわかっていて、それを隠すカプセル化の理由がない場合は、そのタイプとして宣言する必要があります。オブジェクトを作成したばかりなので、具象タイプがわかります。
主な違いは、必要なアクセスのレベルです。そして、抽象化についてのすべての良い答えは動物を含むので、私は言われています。
動物が数頭いるとしましょう-Cat
Bird
とDog
。現在、これらの動物にはいくつかの一般的な動作があります-move()
、eat()
、およびspeak()
。彼らは皆、食べ方や話し方が異なりますが、動物が食べたり話したりする必要がある場合、私は彼らがどうやってそれをするのか本当に気にしません。
しかし、時々私はそうします。私は番犬や番鳥を固めたことはありませんが、番犬は飼っています。誰かが私の家に侵入したとき、私は侵入者を怖がらせるために話すことに頼ることはできません。私は本当に吠えるのに私の犬が必要です-これはただの横糸である彼の話すこととは異なります。
したがって、侵入者を必要とするコードでは、実際にDog fido = getDog(); fido.barkMenancingly();
を実行する必要があります
しかし、ほとんどの場合、私は喜んでどんな動物にもおやつを鳴らしたり、鳴いたり、おやつにつぶやいたりするトリックをさせることができます。 Animal pet = getAnimal(); pet.speak();
より具体的に言うと、ArrayListにはList
にはないsomeメソッドがあります。特に、trimToSize()
。現在、99%のケースでは、これは問題ではありません。 List
は、私たちが気にするほど大きくなることはほとんどありません。しかし、そうなることもあります。したがって、時々、trimToSize()
を実行してバッキング配列を小さくするために、ArrayList
を明確に要求する必要があります。
これは構築時に作成する必要がないことに注意してください。絶対に確実な場合は、returnメソッドまたはキャストを使用して行うこともできます。
一般的に言って、あなたは使うべきです
var x = new Child(); // C#
または
auto x = new Child(); // C++
初期化されたものとは異なる型を変数が持つ正当な理由がない限り、ローカル変数の場合。
(ここでは、C++コードがスマートポインターを使用している可能性が高いという事実を無視しています。実際に知ることができない行が1行もない場合もあります。)
ここでの一般的な考え方は、変数の自動型検出をサポートする言語であり、それを使用するとコードが読みやすくなり、正しいことを実行します(つまり、コンパイラーの型システムは可能な限り最適化し、初期化を新しいものに変更できますタイプは、ほとんどの抽象が使用されていた場合にも機能します)。
このコードは理解しやすく、変更も簡単なので、良いです。
_var x = new Child();
x.DoSomething();
_
意図を伝えるので良い:
_Parent x = new Child();
x.DoSomething();
_
わかりました。一般的で理解しやすいからです。
_Child x = new Child();
x.DoSomething();
_
本当に悪い1つのオプションは、Parent
だけにメソッドDoSomething()
がある場合にChild
を使用することです。それは意図を間違って伝えているので悪いです:
_Parent x = new Child();
(x as Child).DoSomething(); // DON'T DO THIS! IF YOU WANT x AS CHILD, STORE x AS CHILD
_
ここで、質問が具体的に尋ねるケースについてもう少し詳しく説明しましょう。
_Parent x = new Child();
x.DoSomething();
_
後半を関数呼び出しにして、これを別の方法でフォーマットしてみましょう。
_WhichType x = new Child();
FunctionCall(x);
void FunctionCall(WhichType x)
x.DoSomething();
_
これは次のように短縮できます。
_FunctionCall(new Child());
void FunctionCall(WhichType x)
x.DoSomething();
_
ここでは、WhichType
が関数の動作を可能にする最も基本的な/抽象型であることが広く受け入れられていると想定しています(パフォーマンスの懸念がない限り)。この場合、適切なタイプはParent
またはParent
から派生したものになります。
この推論の行は、タイプParent
の使用が適切な選択である理由を説明していますが、天候に恵まれず、他の選択は不適切です(そうではありません)。
tl; dr-Child
よりもParent
を使用することをお勧めしますローカルスコープ内。読みやすさだけでなく、オーバーロードされたメソッド解決が適切に機能し、効率的なコンパイルを可能にすることも必要です。
ローカルスコープでは、
_Parent obj = new Child(); // Works
Child obj = new Child(); // Better
var obj = new Child(); // Best
_
概念的には、可能な限り多くのタイプ情報を維持することです。 Parent
にダウングレードする場合、基本的には有用である可能性のある型情報を取り除いているだけです。
完全な型情報を保持することには、4つの主な利点があります。
見かけの型は、オーバーロードされたメソッドの解決と最適化で使用されます。
例:オーバーロードされたメソッドの解決
_main()
{
Parent parent = new Child();
foo(parent);
Child child = new Child();
foo(child);
}
foo(Parent arg) { /* ... */ } // More general
foo(Child arg) { /* ... */ } // Case-specific optimizations
_
上記の例では、両方のfoo()
がworkを呼び出していますが、1つのケースでは、オーバーロードされたメソッドの解像度が向上しています。
例:コンパイラの最適化
_main()
{
Parent parent = new Child();
var x = parent.Foo();
Child child = new Child();
var y = child .Foo();
}
class Parent
{
virtual int Foo() { return 1; }
}
class Child : Parent
{
sealed override int Foo() { return 2; }
}
_
上記の例では、両方の.Foo()
呼び出しは、最終的には__2
_を返す同じoverride
メソッドを呼び出します。最初のケースでは、正しいメソッドを見つけるための仮想メソッドルックアップがあります。 2番目のケースでは、メソッドのsealed
であるため、この仮想メソッドルックアップは必要ありません。
彼の回答 で同様の例を提供した@Benの功績です。
正確なタイプ、つまりChild
を知ると、コードを読んでいる人に詳細情報が提供され、プログラムの動作を簡単に確認できます。
確かに、parent.Foo();
とchild.Foo();
はどちらも理にかなっているので、実際のコードには関係ないかもしれませんが、コードを初めて見る人にとっては、より多くの情報が役に立ちます。
さらに、開発環境によっては、IDEがChild
よりもParent
に関するより役立つツールチップとメタデータを提供できる場合があります。
最近見たほとんどのC#コード例ではvar
を使用しています。これは基本的にChild
の省略形です。
_Parent obj = new Child(); // Sub-optimal
Child obj = new Child(); // Optimal, but anti-pattern syntax
var obj = new Child(); // Optimal, clean, patterned syntax "everyone" uses now
_
var
以外の宣言ステートメントを見ると、見当違いです。状況に理由がある場合は素晴らしいですが、それ以外の場合はアンチパターンに見えます。
_// Clean:
var foo1 = new Person();
var foo2 = new Job();
var foo3 = new Residence();
// Staggered:
Person foo1 = new Person();
Job foo2 = new Job();
Residence foo3 = new Residence();
_
最初の3つの利点は大きなものでした。これははるかに状況に応じたものです。
それでも、他の人と同じようにコードを使用するユーザーのために、Excelを常に変更しています。 thisバージョンのコードでChild
に固有のメソッドを呼び出す必要はないかもしれませんが、後でコードを再利用または再処理する場合があります。
強力な型システムの利点は、プログラムロジックに関する特定のメタデータを提供し、可能性をより明確にすることです。これはプロトタイピングに非常に役立つので、可能な限りそれを維持することをお勧めします。
Parent
を使用すると、オーバーロードされたメソッド解決が混乱し、一部のコンパイラの最適化が禁止され、リーダーから情報が削除され、コードが見苦しくなります。
var
を使用するのは、実際の方法です。それは素早く、きれいで、パターン化されており、コンパイラとIDEが適切に機能するのを助けます。
重要:この答えはParent
とChild
についてですメソッドのローカルスコープ内。 Parent
とChild
の問題は、戻り値の型、引数、およびクラスフィールドで大きく異なります。
parentType foo = new childType1();
を指定すると、コードはfoo
での親タイプのメソッドの使用に制限されますが、childType1
のインスタンスだけでなく、foo
から派生したタイプのオブジェクトへの参照をparent
に格納できます。新しいchildType1
インスタンスが、foo
が参照を保持する唯一のものである場合、foo
の型をchildType1
として宣言することをお勧めします。一方、foo
が他の型への参照を保持する必要がある場合、それをparentType
として宣言すると、それが可能になります。
私は使用をお勧めします
_Child x = new Child();
_
の代わりに
_Parent x = new Child();
_
前者は情報を失いませんが、後者は情報を失います。
前者を使用すると、後者を使用してできるすべてのことを実行できますが、その逆はできません。
ポインタをさらに詳しく説明するために、複数のレベルの継承があるとします。
Base
-> _DerivedL1
_-> _DerivedL2
_
そして、new DerivedL2()
の結果を変数に初期化したいとします。基本タイプを使用すると、Base
または_DerivedL1
_を使用するオプションが残ります。
使用できます
_Base x = new DerivedL2();
_
または
_DerivedL1 x = new DerivedL2();
_
どちらか一方を優先する論理的な方法は見当たらない。
使用する場合
_DerivedL2 x = new DerivedL2();
_
議論することは何もありません。
常に変数を最も具体的な型として返すか格納することをお勧めしますが、パラメーターは最も広い型として受け入れます。
例えば。
<K, V> LinkedHashMap<K, V> keyValuePairsToMap(List<K> keys, List<V> values) {
//...
}
パラメータは、非常に一般的なタイプであるList<T>
です。 ArrayList<T>
、LinkedList<T>
などは、すべてこのメソッドで受け入れられます。
重要なことに、戻り値の型はLinkedHashMap<K, V>
ではなくMap<K, V>
です。誰かがこのメソッドの結果をMap<K, V>
に割り当てたい場合、それを行うことができます。これは、この結果が単なるマップではなく、順序付けが定義されたマップであることを明確に伝えています。
このメソッドがMap<K, V>
を返したとします。呼び出し元がLinkedHashMap<K, V>
を必要とする場合、型チェック、キャスト、およびエラー処理を行う必要があり、これは本当に面倒です。