GetType () può mentire?


94

Basandomi sulla seguente domanda posta qualche giorno fa in SO: GetType () e polimorfismo e leggendo la risposta di Eric Lippert , ho iniziato a pensare se il fare GetType()non essere virtuale assicurasse davvero che un oggetto non potesse mentire al riguardo Type.

Nello specifico, la risposta di Eric afferma quanto segue:

I progettisti del framework non aggiungeranno una funzionalità incredibilmente pericolosa come consentire a un oggetto di mentire sul suo tipo semplicemente per renderlo coerente con altri tre metodi dello stesso tipo.

Ora la domanda è: posso fare un oggetto che fa menzogna il suo tipo, senza che sia immediatamente evidente? Potrei sbagliarmi profondamente qui e mi piacerebbe un chiarimento se questo è il caso, ma considera il seguente codice:

public interface IFoo
{
    Type GetType();
}

E le seguenti due implementazioni di detta interfaccia:

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

public class NiceFoo : IFoo
{
}

Quindi se esegui il seguente semplice programma:

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();
}

Abbastanza sicuro badFooemette un errore Type.

Ora non so se questo ha delle implicazioni serie sulla base del fatto che Eric descrive questo comportamento come una "caratteristica incredibilmente pericolosa ", ma questo modello potrebbe rappresentare una minaccia credibile?


3
titolo e argomento interessanti!
David

43
IFoo.GetTypee object.GetTypenon è la stessa cosa, quindi qui non sta succedendo niente di brutto tranne lo stile povero. Modifica: in genere GetTypeverrà chiamato su qualche oggetto non noto in fase di compilazione, nella maggior parte dei casi objecte non su qualche interfaccia dubbia. :)
leppie

4
Il tuo titolo, signore, ha reso la mia giornata.
Soner Gönül

5
Devi solo introdurre nuovi membri anche chiamati GetType, con la stessa firma. Ciò non riguarda il GetTypemetodo che è importante. Puoi anche creare un metodo di istanza pubblico che nasconda il GetTypemetodo pertinente , utilizzando la newparola chiave modificatore. Nota, se hai un metodo generico come static Type Test<T>(T t) { return t.GetType(); }(senza vincoli T), cose come Test<IFoo>(new BadFoo())chiameranno ancora il GetTypemetodo originale .
Jeppe Stig Nielsen

2
@ Jamiec - La domanda "potrebbe questo modello rappresentare una minaccia credibile?" non è retorico.
Martin Smith

Risposte:


45

Bella domanda! Per come la vedo io, potresti solo fuorviare un collega sviluppatore se GetType fosse virtuale sull'oggetto, cosa che non è.

Quello che hai fatto è simile allo shadowing di GetType, in questo modo:

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

con questa classe (e utilizzando il codice di esempio da MSDN per il metodo GetType () ) potresti effettivamente avere:

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

quindi, accidenti, hai mentito con successo, giusto? Bene, sì e no ... Considera che usare questo come un exploit significherebbe usare la tua istanza BadFoo come argomento per un metodo da qualche parte, che si aspetta probabilmente un objecttipo di base comune per una gerarchia di oggetti. Qualcosa come questo:

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");
    }
}

ma CheckIfInt(foo)stampa "non un int".

Quindi, fondamentalmente (tornando al tuo esempio), potresti davvero sfruttare il tuo "tipo bugiardo" solo con il codice che qualcuno ha scritto sulla tua IFoointerfaccia, il che è molto esplicito sul fatto che abbia un GetType()metodo "personalizzato" .

Solo se GetType () fosse virtuale sull'oggetto saresti in grado di creare un tipo "bugiardo" che potrebbe essere usato con metodi come CheckIfIntsopra per creare il caos nelle librerie scritte da qualcun altro.


sì, è esattamente lo stesso dell'ombreggiatura. L'ultimo paragrafo è ciò che rende davvero ovvio che non c'è davvero nessuna minaccia. Grazie!
Tra

32

Esistono due modi per essere sicuri del tipo:

  1. Utilizzare typeofsul tipo che non può essere sovraccaricato

    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. Eseguire il cast dell'istanza in an objecte chiamare il GetType()metodo

    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
Come utilizzerai typeofun metodo che ottiene solo un IFoo badFooparametro come?
huysentruitw

typeofnon può essere applicato alle istanze di una classe che è ciò che dobbiamo fare qui. La tua unica opzione è GetType().
Tra

I tuoi due campioni stanno facendo due cose diverse. Le seconde righe non rispondono alla domanda richiesta: esplicitamente "ottengono il tipo BadFoo" ma non "ottengono il tipo di variabile badFoo".
Dan Puzey

Sì, mi dispiace. Come al solito non ho letto la domanda con sufficiente attenzione. Ho aggiornato la mia risposta per sottolineare i due diversi modi di essere sicuro del tipo.
Johannes Wanzek

1
Questo è ciò che stavo facendo notare. Allora qual è il punto del tuo commento? :)
Johannes Wanzek

10

No, non puoi far mentire GetType. Stai solo introducendo un nuovo metodo. Solo il codice che conosce questo metodo lo chiamerà.

Ad esempio, non puoi fare in modo che codice di terze parti o framework chiami il tuo nuovo metodo GetType invece di quello reale, poiché quel codice non sa che il tuo metodo esiste e quindi non lo chiamerà mai.

Puoi tuttavia confondere i tuoi stessi sviluppatori con una tale dichiarazione. Qualsiasi codice compilato con la tua dichiarazione e che utilizzi parametri o variabili digitati come IFoo o qualsiasi tipo derivato da quello utilizzerà effettivamente il tuo nuovo metodo. Ma poiché ciò riguarda solo il tuo codice, non impone realmente una "minaccia".

Se si desidera fornire una descrizione del tipo personalizzata per una classe, è necessario utilizzare un descrittore di tipo personalizzato , magari annotando la classe con TypeDescriptionProviderAttribute . Questo può essere utile in alcune situazioni.


2
+1 per il secondo paragrafo che indica esplicitamente che il codice di terze parti non conosce un'implementazione GetType personalizzata. Altre risposte hanno accennato a quell'idea ma non sono venute fuori e l'hanno detto (almeno non così chiaramente).
brichins

7

Ebbene, in realtà si è già un tipo che può trovarsi in GetType: qualsiasi tipo nullable.

Questo codice :

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

uscite True.


In realtà, non int?è chi sta mentendo, solo il cast implicito objectsi trasforma int?in una scatola int. Ma tuttavia si non si può dire int?da intcon GetType().


1
Quale è il comportamento previsto (o almeno noto). Le risposte a questa domanda spiegano abbastanza chiaramente questo concetto, così come il motivo.
brichins

@brichins: Beh, sono d'accordo che sia noto, ma non sono d'accordo che sia ben noto. Comunque, questo è un caso in cui GetType()produce risultati un po 'strani. In realtà ho chiesto a diversi colleghi se un oggetto non ombreggiato GetType()può restituire qualcosa che differisce dal tipo di runtime dell'oggetto reale, la risposta di tutti è stata "no".
Vlad

5

Non penso che lo farà, dal momento che ogni codice di libreria che chiama GetType dichiarerà la variabile come "Object" o come tipo generico "T"

Il codice seguente:

    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());
    }

stampe:

Object BadFoo dice di essere un 'TypeConcept.BadFoo'

Object NiceFoo dice di essere un 'TypeConcept.

Il tipo generico BadFoo dice di essere un 'TypeConcept.BadFoo'

Il tipo generico NiceFoo dice di essere un 'TypeConcept.NiceFoo'

L'unica volta che questo tipo di codice si tradurrà in cattivi scenari è nel tuo codice, dove dichiari il tipo di parametro come 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 dice di essere un 'System.Int32'

IFoo NiceFoo dice di essere un 'TypeConcept.NiceFoo'


4

La cosa peggiore che può accadere, per quanto ne so, è fuorviare programmatori innocenti a cui capita di usare la classe avvelenata, ad esempio:

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";
}

Se myInstanceè un'istanza di una classe come quella che descrivi nella domanda, verrà trattata come di tipo sconosciuto.

Quindi la mia risposta è no, non vedo nessuna vera minaccia qui.


1
Sicuro. Un programmatore attento può vedere in fase di compilazione quale metodo "GetType" invoca. Object.GetType()è distinto da SomeUserdefinedInterfaceClassOrStruct.GetType(). Solo, se usi il dynamictipo, non puoi mai sapere cosa accadrà al momento del binding. Quindi dovresti usare dynamic x = expression; ... Type t = ((object)x).GetType();in casi come quello.
Jeppe Stig Nielsen

@Jeppe punti giusti! Penso che giustifichi una risposta separata, la mia risposta si concentra maggiormente sul programmatore "innocente" che non starà così attento.
Shadow Wizard is Ear For You

3

Hai alcune opzioni se vuoi giocare al sicuro contro quel tipo di hack:

Trasmetti prima all'oggetto

Puoi chiamare il GetType()metodo originale eseguendo prima il cast dell'istanza a un object:

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

risultati in:

BadFoo says he's a 'ConsoleApplication.BadFoo'

Usa il metodo del modello

L'uso di questo metodo modello ti darà anche il tipo reale:

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

GetType(badFoo);

2

C'è una differenza tra object.GetTypee IFoo.GetType. GetTypeviene chiamato in fase di compilazione su oggetti non conosciuti, e non su Interfacce. Nel tuo esempio, con l'output badFoo.GetTypeè previsto un comportamento, perché sovraccarichi il metodo. L'unica cosa è che altri programmatori possono confondersi con questo comportamento.

Ma se lo usi typeof(), il tipo di output sarà lo stesso e non potrai sovrascriverlo typeof().

Inoltre il programmatore può vedere in fase di compilazione quale metodo GetTypeinvoca.

Quindi alla tua domanda: questo modello non può rappresentare una minaccia credibile, ma non è nemmeno il miglior stile di codifica.


badFoo.GetType()È un comportamento previsto, perché GetTypesovraccarico.
huysentruitw
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.