web-dev-qa-db-ja.com

ダウンキャストの適切な使用法は何ですか?

ダウンキャストとは、基本クラス(またはインターフェイス)からサブクラスまたはリーフクラスへのキャストを意味します。

ダウンキャストの例としては、System.Objectを他のタイプに変更します。

ダウンキャストは人気がなく、おそらくコードの匂いです:オブジェクト指向doctrineは、たとえば、ダウンキャストの代わりに仮想メソッドまたは抽象メソッドを定義して呼び出すことを好みます。

  • ダウンキャストの適切で適切な使用例は何ですか?つまり、ダウンキャストするコードを記述することが適切なのはどのような場合ですか?
  • 答えが「なし」の場合、言語でダウンキャストがサポートされているのはなぜですか?
71
ChrisW

ダウンキャストの適切な使用法をいくつか示します。

そして私は丁重にvehemently同意しませんダウンキャストの使用は間違いなくであるとここで言う他の人と対応する問題を解決する他の合理的な方法はないと私は信じているので、コードのにおいがします。

Equals

class A
{
    // Nothing here, but if you're a C++ programmer who dislikes object.Equals(object),
    // pretend it's not there and we have abstract bool A.Equals(A) instead.
}
class B : A
{
    private int x;
    public override bool Equals(object other)
    {
        var casted = other as B;  // cautious downcast (dynamic_cast in C++)
        return casted != null && this.x == casted.x;
    }
}

Clone

class A : ICloneable
{
    // Again, if you dislike ICloneable, that's not the point.
    // Just ignore that and pretend this method returns type A instead of object.
    public virtual object Clone()
    {
        return this.MemberwiseClone();  // some sane default behavior, whatever
    }
}
class B : A
{
    private int[] x;
    public override object Clone()
    {
        var copy = (B)base.Clone();  // known downcast (static_cast in C++)
        copy.x = (int[])this.x.Clone();  // oh hey, another downcast!!
        return copy;
    }
}

Stream.EndRead/Write

class MyStream : Stream
{
    private class AsyncResult : IAsyncResult
    {
        // ...
    }
    public override int EndRead(IAsyncResult other)
    {
        return Blah((AsyncResult)other);  // another downcast (likely ~static_cast)
    }
}

これらがコードのにおいだと言うつもりなら、それらはより良い解決策を提供する必要があります(不便さのために避けられたとしても)。より良い解決策があるとは思いません。

13
user541686

ダウンキャストは人気がなく、おそらくコードのにおい

同意しません。ダウンキャストは非常に人気があります;膨大な数の実際のプログラムには、1つ以上のダウンキャストが含まれています。そしてそれはたぶんコードの匂いではありません。それは間違いなくコードの匂いです。 ダウンキャスト操作がプログラムのテキストで明示される必要があるのはそのためです。それはあなたがより簡単に匂いに気づき、それにコードレビューの注意を費やすことができるようにするためです。

どのような状況で、ダウンキャストするコードを記述するのが適切ですか?

次のような場合:

  • 式のコンパイル時の型よりも具体的な、式の実行時の型に関する事実について100%正しい知識を持っている。
  • コンパイル時の型では利用できないオブジェクトの機能を使用するには、その事実を利用する必要があります。
  • 最初の2つのポイントのいずれかを排除するためにプログラムをリファクタリングするよりも、キャストを作成するための時間と労力のより良い使用法です。

プログラムを安価にリファクタリングして、ランタイムタイプをコンパイラで推定できるようにするか、プログラムをリファクタリングして、より派生したタイプの機能が不要になった場合は、そうします。 hardおよびexpensiveであるような状況で、プログラムにリファクタリングするためにダウンキャストが言語に追加されました。

言語でダウンキャストがサポートされているのはなぜですか?

C#は、仕事をする実用的なプログラマーによって、for仕事をする実用的なプログラマーによって発明されました。 C#デザイナーはOO純粋主義者ではありません。C#型システムは完璧ではありません。設計上、変数のランタイム型に課せられる制限を過小評価しています。

また、C#ではダウンキャストは非常に安全です。ダウンキャストは実行時に検証されることが強く保証されており、検証できない場合、プログラムは適切に動作し、クラッシュします。これは素晴らしい;つまり、型のセマンティクスを100%正しく理解していることが99.99%正しいことが判明した場合、プログラムは、予測できない動作をしてユーザーデータを0.01%の時間で破壊するのではなく、99.99%の時間で動作し、残りの時間をクラッシュさせます。 。


演習:C#でダウンキャストを生成する方法は少なくとも1つなしで明示的なキャスト演算子を使用します。そのようなシナリオは考えられますか?これらは潜在的なコードの匂いでもあるので、コードにマニフェストをキャストしなくてもダウンキャストクラッシュを引き起こす可能性のある機能の設計にはどのような設計要素が含まれていると思いますか?

139
Eric Lippert

通常、イベントハンドラにはMethodName(object sender, EventArgs e)という署名があります。場合によっては、senderのタイプに関係なく、またはsenderをまったく使用せずにイベントを処理することもできますが、他の場合はsenderをさらにキャストする必要があります-イベントを処理する固有のタイプ。

たとえば、2つのTextBoxsがあり、それぞれのイベントを処理するために単一のデリゲートを使用したいとします。次に、senderTextBoxにキャストして、イベントを処理するために必要なプロパティにアクセスできるようにする必要があります。

private void TextBox_TextChanged(object sender, EventArgs e)
{
   TextBox box = (TextBox)sender;
   box.BackColor = string.IsNullOrEmpty(box.Text) ? Color.Red : Color.White;
}

はい、この例では、内部でこれを行ったTextBoxの独自のサブクラスを作成できます。ただし、常に実用的または可能であるとは限りません。

33
mmathis

somethingがスーパータイプを提供し、サブタイプに応じて異なる方法で処理する必要がある場合は、ダウンキャストが必要です。

decimal value;
Donation d = user.getDonation();
if (d is CashDonation) {
     value = ((CashDonation)d).getValue();
}
if (d is ItemDonation) {
     value = itemValueEstimator.estimateValueOf(((ItemDonation)d).getItem());
}
System.out.println("Thank you for your generous donation of $" + value);

いいえ、これは良いにおいがしません。完全な世界では、Donationには抽象メソッドgetValueがあり、実装する各サブタイプには適切な実装があります。

しかし、これらのクラスがライブラリからのものである場合、変更できない、または変更したくない場合はどうなりますか?次に、他のオプションはありません。

17
Philipp

Eric Lippert's answer に追加するにはコメントできないので...

ジェネリックを使用するようにAPIをリファクタリングすることで、ダウンキャストを回避できることがよくあります。しかし ジェネリックはバージョン2.0まで言語に追加されませんでした 。したがって、独自のコードが古い言語バージョンをサポートする必要がない場合でも、パラメーター化されていないレガシークラスを使用していることに気付く場合があります。たとえば、一部のクラスは、タイプObjectのペイロードを運ぶように定義されている場合があり、実際にそうであることがわかっているタイプにキャストする必要があります。

12
StackOverthrow

静的言語と動的型付け言語の間にはトレードオフがあります。静的型付けは、プログラム(の一部)の安全性についてかなり強力な保証を行うための多くの情報をコンパイラーに提供します。ただし、これにはコストがかかります。プログラムが正しいことを知る必要があるだけでなく、適切なコードを記述して、コンパイラにそうであることを納得させる必要があるからです。言い換えれば、クレームを作ることはそれらを証明することよりも簡単です。

コンパイラに対してunprovenアサーションを作成するのに役立つ「安全でない」構成要素があります。たとえば、 Nullable.Value への無条件の呼び出し、無条件のダウンキャスト、dynamicオブジェクトなどです。これらを使用すると、クレームをアサートできます(「私は、オブジェクトaStringであることをアサートします。 、InvalidCastException ")をスローします証明せずにこれは、価値があることを証明することが非常に難しい場合に役立ちます。

これを悪用するのは危険です。そのため、明示的なダウンキャスト表記が存在し、必須です。これは 構文上のソルト 安全でない操作に注意を引くことを意味します。言語couldは暗黙のダウンキャスト(推論された型が明確である場合)で実装されていますが、これはこの安全でない操作を隠すため、望ましくありません。

ダウンキャストはいくつかの理由で悪いと考えられています。主に私はそれが反OOPであるためだと思います。

OOPは、その「存在理由」が多態性を利用する必要がないことを意味するので、ダウンキャストする必要がなかった場合に本当に気に入っています。

if(object is X)
{
    //do the thing we do with X's
}
else
{
    //of the thing we do with Y's
}

あなたはただやる

x.DoThing()

そして、コードは自動的に魔法のように正しいことをします。

ダウンキャストしない「ハード」な理由がいくつかあります。

  • C++では遅いです。
  • 間違ったタイプを選択すると、ランタイムエラーが発生します。

しかし、いくつかのシナリオでダウンキャストする代わりにかなり醜いことができます。

典型的な例はメッセージ処理で、オブジェクトに処理関数を追加するのではなく、メッセージプロセッサに保持します。次に、処理する配列にMessageBaseClassのトンを入れますが、正しい処理のためにサブタイプごとに1つも必要です。

Double Dispatchを使用して問題を回避できます( https://en.wikipedia.org/wiki/Double_dispatch )...しかし、これにも問題があります。または、これらの難しい理由のリスクがある単純なコードをダウンキャストすることもできます。

しかし、これはジェネリックスが発明される前でした。詳細を後で指定するタイプを提供することで、ダウンキャストを回避できます。

MessageProccesor<T>は、メソッドのパラメーターと戻り値を指定されたタイプによって変化させることができますが、それでも一般的な機能を提供します

もちろん、だれもOOP=コードを書くことを強制していません。反射など、提供されているが眉をひそめている多くの言語機能があります。

3
Ewan

前述のすべてに加えて、オブジェクトタイプのTagプロパティを想像してください。これにより、選択したオブジェクトを別のオブジェクトに格納して、後で必要に応じて使用することができます。このプロパティをダウンキャストする必要があります。

一般的に、今日まで何かを使用していなくても、必ずしも役に立たないことを示しているとは限りません;-)

0
puck

私の仕事でのダウンキャストの最も一般的な使用法は、馬鹿がリスコフの代替原則を破っていることが原因です。

サードパーティのlibにインターフェイスがあると想像してください。

public interface Foo
{
     void SayHello();
     void SayGoodbye();
 }

そしてそれを実装する2つのクラス。 BarおよびQuxBarは適切に動作します。

public class Bar
{
    void SayHello()
    {
        Console.WriteLine(“Bar says hello.”);
    }
    void SayGoodbye()
    {
         Console.WriteLine(“Bar says goodbye”);
    }
}

しかし、Quxはうまく動作していません。

public class Qux
{
    void SayHello()
    {
        Console.WriteLine(“Qux says hello.”);
    }
    void SayGoodbye()
    {
        throw new NotImplementedException();
    }
}

ええと...今、私には選択肢がありません。私はhaveにチェックを入力し、(おそらく)ダウンキャストして、プログラムがクラッシュするのを防ぎます。

0
RubberDuck

別の実際の例は、WPFのVisualTreeHelperです。ダウンキャストを使用してDependencyObjectVisualおよびVisual3Dにキャストします。たとえば、 VisualTreeHelper.GetParent です。ダウンキャストは VisualTreeUtils.AsVisualHelper で発生します:

private static bool AsVisualHelper(DependencyObject element, out Visual visual, out Visual3D visual3D)
{
    Visual elementAsVisual = element as Visual;

    if (elementAsVisual != null)
    {
        visual = elementAsVisual;
        visual3D = null;
        return true;
    }

    Visual3D elementAsVisual3D = element as Visual3D;

    if (elementAsVisual3D != null)
    {
        visual = null;
        visual3D = elementAsVisual3D;
        return true;
    }            

    visual = null;
    visual3D = null;
    return false;
}
0
zwcloud