GetType () może kłamać?


94

Opierając się na pytaniu zadanym kilka dni temu w SO: GetType () i polimorfizmie oraz czytając odpowiedź Erica Lipperta , zacząłem się zastanawiać, czy tworzenie GetType()nie jest wirtualne naprawdę zapewni, że obiekt nie może kłamać na jego temat Type.

W szczególności odpowiedź Erica brzmi następująco:

Projektanci frameworka nie zamierzają dodawać niewiarygodnie niebezpiecznej funkcji, takiej jak pozwalanie obiektowi kłamać na temat jego typu, tylko po to, aby był zgodny z trzema innymi metodami tego samego typu.

Teraz pytanie brzmi: czy mogę stworzyć obiekt, który kłamie na temat swojego typu, nie będąc od razu oczywistym? Mogę się tutaj głęboko mylić i chciałbym wyjaśnić, jeśli tak jest, ale weź pod uwagę następujący kod:

public interface IFoo
{
    Type GetType();
}

I następujące dwie implementacje wspomnianego interfejsu:

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

public class NiceFoo : IFoo
{
}

Następnie, jeśli uruchomisz następujący prosty program:

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

Z pewnością badFoowyprowadza błąd Type.

Teraz nie wiem, czy ma to jakieś poważne konsekwencje, ponieważ Eric opisuje to zachowanie jako „ niezwykle niebezpieczną cechę ”, ale czy ten wzorzec może stanowić wiarygodne zagrożenie?


3
ciekawy tytuł i temat!
David

43
IFoo.GetTypei object.GetTypeto nie to samo, więc nie dzieje się tu nic złego poza kiepskim stylem. Edycja: Generalnie GetTypezostanie wywołany na jakimś obiekcie nieznanym w czasie kompilacji, w większości przypadków, objecta nie na jakimś podejrzanym interfejsie. :)
leppie

4
Twój tytuł, panie, uczynił mój dzień.
Soner Gönül

5
Po prostu wprowadzasz nowych członków, również nazywanych GetType, z tym samym podpisem. Nie dotyczy to GetTypemetody, która jest ważna. Możesz również utworzyć publiczną metodę instancji, która ukrywa odpowiednią GetTypemetodę, używając newsłowa kluczowego modifier. Zauważ, że jeśli masz metodę ogólną, taką jak static Type Test<T>(T t) { return t.GetType(); }(bez ograniczeń T), to takie rzeczy Test<IFoo>(new BadFoo())będą nadal wywoływać oryginalną GetTypemetodę.
Jeppe Stig Nielsen

2
@Jamiec - Pytanie „czy ten wzór może stanowić wiarygodne zagrożenie?” nie jest retoryczne.
Martin Smith

Odpowiedzi:


45

Fajne pytanie! Z mojego punktu widzenia można naprawdę zmylić innego programistę tylko wtedy, gdy GetType był wirtualny na obiekcie, a tak nie jest.

To, co zrobiłeś, jest podobne do shadowing GetType, na przykład:

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

z tą klasą (i używając przykładowego kodu z MSDN dla metody GetType () ) możesz rzeczywiście mieć:

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

więc, yikes, udało ci się skłamać, prawda? Cóż, tak i nie ... Weź pod uwagę, że użycie tego jako exploita oznaczałoby użycie Twojej instancji BadFoo jako argumentu w jakiejś metodzie, która oczekuje prawdopodobnie objectwspólnego typu podstawowego dla hierarchii obiektów. Coś takiego:

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

ale CheckIfInt(foo)wypisuje „nie int”.

Tak więc, w zasadzie (wracając do przykładu), mógłbyś wykorzystać swój „kłamliwy typ” tylko za pomocą kodu, który ktoś napisał na Twoim IFoo interfejsu, co jest bardzo wyraźne w tym, że ma on „niestandardową” GetType()metodę.

Tylko jeśli GetType () byłby wirtualny na obiekcie, byłbyś w stanie stworzyć „kłamliwy” typ, który mógłby być użyty z metodami takimi jak CheckIfIntpowyżej, aby siać spustoszenie w bibliotekach napisanych przez kogoś innego.


tak, to dokładnie to samo, co cieniowanie. Ostatni akapit jest tym, co naprawdę pokazuje, że tak naprawdę nie ma zagrożenia. Dzięki!
między

32

Istnieją dwa sposoby upewnienia się co do typu:

  1. Użyj typeofna typie, którego nie można przeciążać

    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. Rzutuj instancję na objecti wywołaj GetType()metodę

    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
Jak użyjesz typeofmetody, która pobiera tylko IFoo badFooparametr jako?
huysentruitw

typeofnie można zastosować do instancji klasy, co musimy tutaj zrobić. Twoja jedyna opcja to GetType().
Między

Twoje dwie próbki robią dwie różne rzeczy. Drugie wiersze nie odpowiadają na wymagane pytanie - jawnie „pobierają typ BadFoo”, ale nie „określają typu zmiennej badFoo”.
Dan Puzey

Tak, przepraszam. Jak zwykle nie przeczytałem wystarczająco uważnie pytania. Zaktualizowałem moją odpowiedź, aby wskazać dwa różne sposoby uzyskania pewności co do typu.
Johannes Wanzek

1
To właśnie wskazywałem. Jaki jest więc sens twojego komentarza? :)
Johannes Wanzek

10

Nie, nie możesz skłamać GetType. Wprowadzasz tylko nową metodę. Wywoła ją tylko kod, który zna tę metodę.

Nie możesz na przykład sprawić, aby kod strony trzeciej lub struktury wywoływał Twoją nową metodę GetType zamiast prawdziwej, ponieważ ten kod nie wie, że Twoja metoda istnieje i dlatego nigdy jej nie wywoła.

Możesz jednak pomylić własnych programistów z taką deklaracją. Każdy kod, który jest skompilowany z twoją deklaracją i który używa parametrów lub zmiennych wpisanych jako IFoo lub dowolnego typu pochodnego, faktycznie będzie używał twojej nowej metody. Ale ponieważ wpływa to tylko na twój własny kod, tak naprawdę nie stanowi „zagrożenia”.

Jeśli chcesz podać niestandardowy opis typu dla klasy, należy to zrobić za pomocą niestandardowego deskryptora typu , na przykład przez dodanie adnotacji do klasy za pomocą TypeDescriptionProviderAttribute . Może to być przydatne w niektórych sytuacjach.


2
+1 w drugim akapicie wyraźnie wskazując, że kod innej firmy nie wie o niestandardowej implementacji GetType. Inne odpowiedzi wskazywały na ten pomysł, ale tak naprawdę nie wyszły i nie mówiły (przynajmniej nie tak wyraźnie).
brichins

7

Cóż, faktycznie nie jest już typ, który może leżeć w GetType: wszelkiego rodzaju pustych.

Ten kod :

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

wyjścia True.


Właściwie to nie ten, int?kto kłamie, po prostu ukryty rzut objectzamienia int?się w pudełko int. Ale mimo to ty nie może powiedzieć int?z into GetType().


1
Jakie jest oczekiwane (lub przynajmniej dobrze znane) zachowanie. Odpowiedzi na to pytanie dość jasno wyjaśniają tę koncepcję, a także jej przyczynę.
brichins

@brichins: Cóż, zgadzam się, że jest znane, ale nie mogę się zgodzić, że jest dobrze znane. W każdym razie jest to przypadek, w którym GetType()daje nieco dziwny wynik. Właściwie zapytałem kilku kolegów o to, czy nie-cieniowany GetType()może zwrócić coś, co różni się od rzeczywistego typu środowiska wykonawczego obiektu, wszyscy odpowiedzieli „nie”.
Vlad

5

Nie sądzę, aby tak było, ponieważ każdy kod biblioteki, który wywołuje GetType, będzie deklarował zmienną jako „Obiekt” lub jako typ ogólny „T”

Poniższy kod:

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

wydruki:

Obiekt BadFoo mówi, że jest „TypeConcept.BadFoo”

Obiekt NiceFoo mówi, że jest „TypeConcept.NiceFoo”

Typ ogólny BadFoo mówi, że jest „TypeConcept.BadFoo”

Typ ogólny NiceFoo mówi, że jest „TypeConcept.NiceFoo”

Jedynym przypadkiem, gdy ten rodzaj kodu spowoduje zły scenariusz, jest twój własny kod, w którym deklarujesz typ parametru jako 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 mówi, że jest „System.Int32”

IFoo NiceFoo mówi, że jest „TypeConcept.NiceFoo”


4

Najgorsze, co może się zdarzyć, o ile wiem, to wprowadzanie w błąd niewinnych programistów, którzy używają zatrutej klasy, na przykład:

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

Jeśli myInstancejest to instancja klasy takiej jak opisana w pytaniu, będzie ona traktowana jako typ nieznany.

Więc moja odpowiedź brzmi: nie, nie widzę tutaj żadnego prawdziwego zagrożenia.


1
Pewnie. Uważny programista może zobaczyć w czasie kompilacji, którą metodę "GetType" wywołuje. Object.GetType()różni się od SomeUserdefinedInterfaceClassOrStruct.GetType(). Tylko jeśli używasz dynamictypu, nigdy nie możesz wiedzieć, co się stanie w czasie wiązania. Dlatego powinieneś używać dynamic x = expression; ... Type t = ((object)x).GetType();w takich przypadkach.
Jeppe Stig Nielsen

@Jeppe fair points! Myślę, że uzasadnia to oddzielną odpowiedź, moja odpowiedź skupia się bardziej na „niewinnym” programistce, który nie będzie tak ostrożny.
Shadow Wizard is Ear For You

3

Masz kilka opcji, jeśli chcesz grać bezpiecznie przeciwko tego rodzaju włamaniom:

Najpierw rzut na obiekt

Możesz wywołać oryginalną GetType()metodę, najpierw rzutując instancję na object:

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

prowadzi do:

BadFoo says he's a 'ConsoleApplication.BadFoo'

Użyj metody szablonowej

Użycie tej metody szablonu daje również prawdziwy typ:

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

GetType(badFoo);

2

Istnieje różnica między object.GetTypei IFoo.GetType. GetTypejest wywoływana w czasie kompilacji na nieznanych obiektach, a nie na interfejsach. W twoim przykładzie z wyjściem badFoo.GetTypeoczekuje się zachowania, ponieważ przeciążasz metodę. Chodzi tylko o to, że inni programiści mogą się pomylić z tym zachowaniem.

Ale jeśli typeof()go użyjesz , wyświetli się, że typ jest taki sam i nie możesz go nadpisać typeof().

Również programista może zobaczyć w czasie kompilacji, którą metodę GetTypewywołuje.

A więc do twojego pytania: ten wzorzec nie może stanowić wiarygodnego zagrożenia, ale nie jest też najlepszym stylem kodowania.


badFoo.GetType()JEST oczekiwane zachowanie, ponieważ GetTypezostało przeciążone.
huysentruitw
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.