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а не для какого-то хитроумного интерфейса. :)
— leppie

4
Ваш титул сэр, сделал мой день.
— Soner Gönül

5
Вы просто представляете новых участников GetTypeс той же подписью. Это не имеет отношения к GetTypeважному методу. Вы также можете создать метод общедоступного экземпляра, который скрывает соответствующий GetTypeметод, используя newключевое слово модификатора. Обратите внимание: если у вас есть общий метод, например static Type Test<T>(T t) { return t.GetType(); }(без ограничений T), то такие вещи все равно Test<IFoo>(new BadFoo())будут вызывать исходный GetTypeметод.
— Йеппе Стиг Нильсен

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».
— Дэн Пьюзи

Да, мне очень жаль. Как обычно, я недостаточно внимательно прочитал вопрос. Я обновил свой ответ, чтобы указать на два разных способа быть уверенным в типе.
— Йоханнес Ванзек

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

печатает:

Object BadFoo говорит, что он TypeConcept.BadFoo

Объект NiceFoo говорит, что он TypeConcept.NiceFoo

Generic Type 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 справедливые очки! Я думаю, что это оправдывает отдельный ответ, мой ответ больше касается "невинного" программиста, который не будет так осторожен.
— Shadow Wizard is Ear For You

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ожидается bahaviour, потому что вы перегружаете метод. Единственное, что другие программисты могут запутаться в таком поведении.

Но если вы typeof()его используете, вы увидите, что тип тот же, и вы не можете перезаписать typeof().

Также программист может видеть во время компиляции, какой метод GetTypeон вызывает.

Итак, на ваш вопрос: этот шаблон не может представлять серьезной угрозы, но это также не лучший стиль кодирования.


badFoo.GetType()ЕСТЬ ожидаемое поведение, потому что GetTypeбыл перегружен.
— huysentruitw
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.