GetType()は嘘をつきますか?


94

数日前にSOで質問された次の質問に基づいて:GetType()とポリモーフィズム、およびEric Lippertの回答を読んで、GetType()仮想化しないことでオブジェクトがそのについて嘘をついていないことを本当に保証するかと考え始めましたType

具体的には、エリックの答えは次のように述べています:

フレームワークの設計者は、オブジェクトをその型について偽ることができるようにするなど、同じ型の他の3つのメソッドと一貫性を持たせるなど、非常に危険な機能を追加しません。

さて、問題は次のとおりです。その型について偽りのないオブジェクトを、すぐに明確にせずに作成できますか?私はここで深刻な誤りを犯している可能性があり、それが事実である場合は説明が必要ですが、次のコードを検討してください。

public interface IFoo
{
    Type GetType();
}

上記のインターフェースの次の2つの実装:

public class BadFoo : IFoo
{
    Type IFoo.GetType()
    {
        return typeof(int);
    }
}

public class NiceFoo : IFoo
{
}

次に、次の簡単なプログラムを実行すると、

static void Main(string[] args)
{
    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    Console.ReadLine();
}

確かにbadFoo誤っTypeた出力をします。

エリックがこの動作を「信じられないほど危険な機能」として説明していることに基づいて、これが深刻な影響を持っているかどうかはわかりませんが、このパターンが信頼できる脅威をもたらす可能性はありますか?


3
興味深いタイトルとトピック!
デビッド

43
IFoo.GetTypeそしてobject.GetType何も悪いが悪いのスタイルを除いて、ここで何が起こっているので、同じものではありません。編集:通常GetType、コンパイル時に不明なオブジェクトで呼び出されobjectます。ほとんどの場合、一部の危険なインターフェイスではありません。:)
leppie 2013年

4
あなたの肩書き、私の日を作りました。
SonerGönül2013年

5
同じ署名のと呼ばれる新しいメンバーを紹介するだけGetTypeです。それGetTypeは重要な方法とは関係ありません。modifierキーワードGetTypeを使用して、関連するメソッドを非表示にするパブリックインスタンスメソッドを作成することもできますnewstatic Type Test<T>(T t) { return t.GetType(); }(に制約がないT)のようなジェネリックメソッドがある場合Test<IFoo>(new BadFoo())でも、同様のものは元のGetTypeメソッドを呼び出します。
Jeppe Stig Nielsen 2013

2
@Jamiec-「このパターンが信頼できる脅威をもたらすことができますか?」という質問 修辞的ではありません。
マーティン・スミス

回答:


45

いい質問だ!私の見たところ、GetTypeがオブジェクトに対して仮想である場合にのみ、他の開発者を本当に誤解させることができましたが、そうではありません。

あなたがしたことは、このようにGetTypeをシャドウイングすることに似ています:

public class BadFoo
{
    public new Type GetType()
    {
        return typeof(int);
    }
}

このクラスを使用すると(そして、MSDNのGetType()メソッドのサンプルコードを使用すると)、実際に次のようになります。

int n1 = 12;
BadFoo foo = new BadFoo();

Console.WriteLine("n1 and n2 are the same type: {0}",
                  Object.ReferenceEquals(n1.GetType(), foo.GetType())); 
// output: 
// n1 and n2 are the same type: True

そう、うーん、あなたはうそをついて成功しましたよね?ええ、はい、いいえ...これをエクスプロイトとして使用することは、BadFooインスタンスをメソッドの引数としてどこかで使用することを意味しますobject。このようなもの:

public void CheckIfInt(object ob)
{
    if(ob.GetType() == typeof(int))
    {
        Console.WriteLine("got an int! Initiate destruction of Universe!");
    }
    else
    {
        Console.WriteLine("not an int");
    }
}

しかし、CheckIfInt(foo)「intではない」と出力します。

したがって、基本的に(例に戻ると)、「嘘のタイプ」を実際に利用できるのは、誰かがあなたのIFooインターフェースに対して書いたコードだけGetType()です。これは、「カスタム」メソッドがあるという事実について非常に明確です。

GetType()がオブジェクトに対して仮想である場合にのみ、CheckIfInt他の誰かが作成したライブラリに大混乱を作成するために、上記のようなメソッドで使用できる「横たわる」タイプを作成できます。


ええ、それはシャドウイングとまったく同じです。最後の段落は、本当に脅威がないことを本当に明らかにするものです。ありがとう!
2013年

32

タイプを確認する方法は2つあります。

  1. typeofオーバーロードできないタイプで使用

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", typeof(BadFoo));
    Console.WriteLine("NiceFoo really is a '{0}'", typeof(NiceFoo));
    Console.ReadLine();
    
  2. インスタンスをにキャストしobjectGetType()メソッドを呼び出します

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", ((object)badFoo).GetType());
    Console.WriteLine("NiceFoo really is a '{0}'", ((object)niceFoo).GetType());
    Console.ReadLine();
    

1
asパラメーターtypeofのみを取得するメソッドでどのように使用しIFoo badFooますか?
huysentruitw 2013年

typeofここで行う必要のあるクラスのインスタンスには適用できません。唯一の選択肢はGetType()です。
2013年

2つのサンプルは2つの異なることを行っています。2行目は、必要な質問には答えません。明示的に「BadFoo型を取得」しますが、badFoo「変数の型を取得」しません。
Dan Puzey 2013年

うん、ごめんなさい。いつものように私は質問を十分に注意深く読みませんでした。タイプを確認する2つの異なる方法を指摘するために、回答を更新しました。
Johannes Wanzek 2013年

1
これは私が指摘していたことです。では、コメントの意味は何ですか?:)
Johannes Wanzek 2013年

10

いいえ、GetTypeを嘘にすることはできません。あなたは新しい方法を導入しているだけです。このメソッドを認識するコードのみがこのメソッドを呼び出します。

たとえば、サードパーティまたはフレームワークのコードで実際のメソッドではなく新しいGetTypeメソッドを呼び出すことはできません。そのコードは、メソッドが存在することを認識していないため、メソッドを呼び出さないからです。

ただし、このような宣言で自分の開発者を混乱させることができます。宣言を使用してコンパイルされ、IFooとして型指定されたパラメーターまたは変数またはそれから派生した型を使用するコードは、実際には代わりに新しいメソッドを使用します。しかし、それはあなた自身のコードにのみ影響を与えるので、それは本当に「脅威」を課しません。

あなたはクラスのカスタム型の説明を提供したいならば、これは使用して行われるべきであるカスタム・タイプ記述を多分にあなたのクラスに注釈を付けることで、TypeDescriptionProviderAttribute。これは、状況によっては役立ちます。


2
2番目の段落の+1は、サードパーティのコードがカスタムのGetType実装を認識していないことを明示的に示しています。他の答えはその考えをほのめかしましたが、実際には出てそれを言っていませんでした(少なくともそれほど明確ではありません)。
ブリッチン2013年

7

ええと、実際には、すでに存在GetTypeする可能性のある型があります。

このコード

int? x = 0; int y = 0;
Console.WriteLine(x.GetType() == y.GetType());

出力True


実は、そうではありませんint?に横たわっている、ただの暗黙的なキャストobjectターンint?箱入りへint。しかし、それでも、と区別できませんint?intGetType()


1
これは予想される(または少なくともよく知られている)動作です。回答この質問はかなり明確にこの概念だけでなく、その理由を説明しています。
ブリッチン2013年

@brichins:まあ、私はそれは知られています同意するが、私はそれがだ賛成できないだけでなく -known。とにかく、これはGetType()やや奇妙な結果を生むケースです。私は実際にいくつかの同僚に、非シャドウGetType()が実際のオブジェクトのランタイムタイプとは異なるものを返すことができるかどうか尋ねましたが、全員の答えは「いいえ」でした。
ヴラド

5

GetTypeを呼び出すすべてのライブラリコードは変数を「オブジェクト」またはジェネリック型「T」として宣言するため、私はそうは思わない

次のコード:

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintObjectType("BadFoo", badFoo);
        PrintObjectType("NiceFoo", niceFoo);
        PrintGenericType("BadFoo", badFoo);
        PrintGenericType("NiceFoo", niceFoo);
    }

    public static void PrintObjectType(string actualName, object instance)
    {
        Console.WriteLine("Object {0} says he's a '{1}'", actualName, instance.GetType());
    }

    public static void PrintGenericType<T>(string actualName, T instance)
    {
        Console.WriteLine("Generic Type {0} says he's a '{1}'", actualName, instance.GetType());
    }

プリント:

オブジェクトBadFooは彼が 'TypeConcept.BadFoo'だと言っています

オブジェクトNiceFooは、彼が「TypeConcept.NiceFoo」だと言っています

ジェネリック型BadFooは彼が「TypeConcept.BadFoo」だと言っています

ジェネリック型NiceFooは「TypeConcept.NiceFoo」だと言っています

この種類のコードが悪いシナリオを引き起こす唯一の時はあなたがあなたのコードである、そこであなたはパラメータタイプをIFooとして宣言する

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintIFoo("BadFoo", badFoo);
        PrintIFoo("NiceFoo", niceFoo);
    }

    public static void PrintIFoo(string actualName, IFoo instance)
    {
        Console.WriteLine("IFoo {0} says he's a '{1}'", actualName, instance.GetType());
    }

IFoo BadFooは彼が 'System.Int32'だと言っています

IFoo NiceFooは彼が 'TypeConcept.NiceFoo'だと言っています


4

私の知る限りでは、最悪の場合、たまたま毒化されたクラスを使用する、誤解を招くような罪のないプログラマーがいます。

Type type = myInstance.GetType();
string fullName = type.FullName;
string output;
if (fullName.Contains(".Web"))
{
    output = "this is webby";
}
else if (fullName.Contains(".Customer"))
{
    output = "this is customer related class";
}
else
{
    output = "unknown class";
}

myInstance質問で説明したようなクラスのインスタンスである場合、それは不明なタイプとして扱われます。

だから私の答えはノーです。


1
承知しました。注意深いプログラマーは、コンパイル時にどのメソッド「GetType」を呼び出すかを確認できます。Object.GetType()とは異なりSomeUserdefinedInterfaceClassOrStruct.GetType()ます。ただし、dynamicタイプを使用する場合、バインディング時に何が発生するかを知ることはできません。そのdynamic x = expression; ... Type t = ((object)x).GetType();ような場合に使用する必要があります。
Jeppe Stig Nielsen 2013

@Jeppeフェアポイント!私はそれが別の答えを正当化すると思います、私の答えはそれほど注意しないだろう「無実の」プログラマにもっと焦点を合わせます。
シャドウウィザードはあなたのための耳です

3

この種のハッキングに対して安全にプレイしたい場合は、いくつかのオプションがあります。

最初にオブジェクトにキャスト

GetType()最初にインスタンスをにキャストすることにより、元のメソッドを呼び出すことができますobject

 Console.WriteLine("BadFoo says he's a '{0}'", ((object)badFoo).GetType());

結果は:

BadFoo says he's a 'ConsoleApplication.BadFoo'

テンプレート方式を使用

このテンプレートメソッドを使用すると、実際のタイプも得られます。

static Type GetType<T>(T obj)
{
    return obj.GetType();
}

GetType(badFoo);

2

とには違いがobject.GetTypeありIFoo.GetTypeます。GetTypeインターフェースではなく、不明なオブジェクトに対してコンパイル時に呼び出されます。あなたの例では、出力badFoo.GetTypeでは、メソッドをオーバーロードするため、予想される動作です。唯一のことは、他のプログラマーがこの動作について混乱する可能性があるということです。

ただし、使用typeof()すると、タイプが同じであることが出力され、上書きできませんtypeof()

また、プログラマはコンパイル時に、どのメソッドGetTypeを呼び出すかを確認できます。

だからあなたの質問に:このパターンは信頼できる脅威をもたらすことはできませんが、コーディングの最良のスタイルでもありません。


badFoo.GetType()GetTypeオーバーロードされたため、予想される動作です。
huysentruitw 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.