GetType () може брехати?


94

Виходячи з наступного запитання, яке було задано кілька днів тому в SO: GetType () та поліморфізму та читання відповіді Еріка Ліпперта , я почав думати, чи GetType()не бути віртуальним насправді гарантує, що об’єкт не може брехати про нього Type.

Зокрема, у відповіді Еріка зазначено наступне:

Дизайнери фреймворку не збираються додавати неймовірно небезпечну функцію, таку як дозволяти об’єкту брехати про свій тип, лише щоб зробити його узгодженим з трьома іншими методами того ж типу.

Тепер питання: чи можу я зробити об’єкт, який бреше про свій тип, не відразу це очевидно? Я можу тут глибоко помилятися, і я хотів би пояснити, якщо це так, але враховуйте наступний код:

public interface IFoo
{
    Type GetType();
}

І наступні дві реалізації згаданого інтерфейсу:

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а не якийсь хитрий інтерфейс. :)
леппі

4
Ваш титул, сер, зробив мені день.
Soner Gönül

5
Ви просто представляєте нових членів, яких також називають GetType, з тим самим підписом. Це не стосується GetTypeважливого методу. Ви також можете зробити відкритий метод примірника, який приховує відповідний GetTypeметод, використовуючи newключове слово modifier. Зверніть увагу, що якщо у вас є загальний метод типу static Type Test<T>(T t) { return t.GetType(); }(без обмежень T), такі речі Test<IFoo>(new BadFoo())все одно викликатимуть оригінальний GetTypeметод.
Jeppe Stig Nielsen

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вище, для створення хаосу в бібліотеках, написаних кимось іншим.


так, це точно те саме, що тінь. Останній параграф - це те, що насправді дає зрозуміти, що насправді немає загрози. Дякую!
Між

32

Є два способи бути впевненими в Типі:

  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. Перетворіть екземпляр на objectвиклик GetType()методу

    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
Як ви будете використовувати typeofметод, який отримує лише IFoo badFooпараметр as?
huysentruitw

typeofне може бути застосована до екземплярів класу, що саме тут нам потрібно робити. Твій єдиний варіант - GetType().
Між

Ваші два зразки роблять дві різні речі. Другі рядки не відповідають на необхідне запитання - вони явно "отримують тип BadFoo", але не "отримують тип змінної badFoo".
Dan Puzey

Так, вибачте. Як зазвичай, я недостатньо уважно прочитав питання. Я оновив свою відповідь, щоб вказати на два різні способи бути впевненими у типі.
Йоханнес Ванзек,

1
На це я вказував. То який сенс вашого коментаря? :)
Йоханнес Ванзек,

10

Ні, ви не можете змусити GetType брехати. Ви лише представляєте новий метод. Викличе його лише код, який знає про цей метод.

Наприклад, ви не можете змусити сторонній код або фреймворк-код викликати ваш новий метод GetType замість реального, оскільки цей код не знає, що ваш метод існує, і тому його ніколи не буде викликати.

Однак ви можете заплутати власних розробників такою декларацією. Будь-який код, який скомпільований з вашою декларацією, і який використовує параметри або змінні, введені як IFoo, або будь-який тип, отриманий з цього, дійсно використовуватиме ваш новий метод. Але оскільки це впливає лише на ваш власний код, це насправді не становить "загрози".

Якщо ви хочете надати опис власного типу для класу, це слід зробити за допомогою дескриптора спеціального типу , можливо, анотуючи свій клас TypeDescriptionProviderAttribute . Це може бути корисно в деяких ситуаціях.


2
+1 для вашого другого абзацу прямо вказуючи, що сторонній код не знає про власну реалізацію GetType. Інші відповіді натякали на цю ідею, але насправді не виходили і не говорили її (принаймні, не настільки чітко).
brichins

7

Ну, на самому ділі це вже тип , який може лежати в GetType: будь-який обнуляє типу.

Цей код :

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

виходи True.


Насправді, не int?хто бреше, а лише неявний актор, який objectперетворюється int?на коробку int. Але тим не менш, ви не можете сказати, int?з intс GetType().


1
Яка очікувана (або принаймні добре відома) поведінка. Відповіді на це питання досить чітко пояснюють це поняття, а також причину.
brichins

@brichins: Ну, я згоден, що це відомо, але я не можу погодитися, що це добре відомо. У будь-якому випадку, це випадок, коли GetType()дається дещо дивний результат. Я насправді запитував кількох колег про те, чи GetType()може нетіньовий повернути щось, що відрізняється від фактичного типу виконання об'єкта, відповідь кожного була "ні".
Влад

5

Я не думаю, що це буде, оскільки кожен бібліотечний код, який викликає GetType, оголосить змінну як 'Object' або як загальний тип '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

@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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.