web-dev-qa-db-ja.com

親インターフェースから派生クラスの実際のタイプを取得する方法

次のようなコード部分があるとします。

IProduct product = ProductCreator.CreateProduct(); //Factory method we have here
SellThisProduct(product);

//...

private void SellThisProduct(IProduct product)
{
  //.. Do something here
}

//...

internal class Soda : IProduct
{}

internal class Book : IProduct
{}

メソッドでどの製品が実際にSellThisProduct()メソッドに渡されるかをどのように推測できますか?

GetType()などと言った場合、おそらくIProductタイプを返します。

34
Tarik

GetTypeは、オブジェクトの正確なランタイムタイプを取得します。 ドキュメントから:

現在のインスタンスの正確なランタイムタイプを表すTypeインスタンス。

isを使用して、オブジェクトが特定のタイプのインスタンスであるかどうかを判別することもできます。

var noise = (obj is Velociraptor) ? "SKREEE!" : "<unknown>";

正確なランタイムタイプが必要なのはなぜですか?インターフェースの要点は、共通のインターフェースの背後にある実装の詳細を隠すべきであるということです。タイプに基づいてアクションを実行する必要がある場合、それが提供するカプセル化に違反しているという大きなヒントになります。

1つの代替方法は、ポリモーフィズムを使用することです。

public interface IVocalizer { string Talk(); }

public class Doorbell : IVocalizer {
  public string Talk() { return "Ding-dong!" }
}
public class Pokemon : IVocalizer {
  public string Talk() {
    var name = this.GetType().ToString();
    return (name + ", " + name + "!").ToUpper(); } // e.g., "PIKACHU, PIKACHU!"
}
public class Human : IVocalizer {
  public string Talk() { return "Hello!"; }
}

これらの3つのタイプはまったく関連していないため、共通のタイプからの継承は意味がありません。しかし、それらがノイズを生成する同じ機能を共有していることを表すために、IVocalizerインターフェイスを使用して、それぞれにノイズを生成するように要求できます。これは非常にすっきりしたアプローチです。これで、オブジェクトにノイズを発生させたいときに、オブジェクトのタイプを気にする必要がなくなりました。

IVocalizer talker = new ???();  // Anything that's an IVocalizer can go here.

// elsewhere:
Console.WriteLine(talker.Talk());    // <-- Now it doesn't matter what the actual type is!
                                     //   This will work with any IVocalizer and you don't
                                     //   need to know the details.
58
John Feminella

Object.GetTypeは、インスタンスの 正確なランタイムタイプ を返します。それを使用する必要があります。

一般的に言えば、インターフェイスの実行時のタイプが何であるかをまったく気にする必要はありませんが、それを決定するコードを記述している場合、おそらくどこかでデザインのエラーを反映しています。

実際、インターフェース名IProductは、すでにコードのにおいがします。それ自体は間違っていませんが、インターフェイスは特定のオブジェクトで利用可能なactionsを定義することを目的としています。つまり、doesです。 IProductという名前は、その内容isではなく、doesを表しているようです。これは、抽象基本クラスに適しています。これは「ルール」ではありませんが、従うのに適したガイドラインです。

抽象型(基本クラスまたはインターフェース)に依存するメソッド/クラスを作成するときに、より派生した型または特定の実装に依存している場合、それは抽象型が貧弱であることを意味します(十分な機能がない)または、依存関係が多すぎる(抽象化ではなく実装の詳細に依存する)。

Product/IProductを拡張してさらに多くのことを行うか、メソッドのオーバーロードによって依存関係をspecific製品タイプで実際に機能させることを検討してください。

11
Aaronaught
if (product is Book)
{
   ...
}
else if (product is Soda)
{
   ...
}
4
Igby Largeman

GetType()は実際のタイプを返しますが、is演算子を使用する必要があります。

通常、これを行う必要はありません。通常、実行することは、動作を子クラスに任せ、インターフェイスを介して呼び出すことです。たとえば、SodaとBookの要件が異なる場合(たとえば、Sodaは税金を徴収する必要がありますが、Bookは徴収しません)、インターフェースでSellメソッドを作成し、SellThisProduct()メソッドで次のようにします。 d単にオブジェクトでSell()メソッドを呼び出します。

public interface IProduct
{
   public decimal Sell(); // computes price
   ...
}

.....

IProduct product = ProductCreator.CreateProduct(); //Factory Method we have here
SellThisProduct(product);

//...

private void SellThisProduct(IProduct product)
{
   var price = product.Sell();
   ...
}

internal class Soda : IProduct
{
   public decimal Sell()
   {
       this.Price + TaxService.ComputeTax( this.Price );
   }
}

internal class Book : IProduct
{
    public decimal Sell()
    {
         return this.Price;
    }
}
4
tvanfosson

typeof(product)はIProductを返します。

product.GetType()は、メンバー関数であるため、実際にはオブジェクトの派生型を返します。

1
David