GetType() может лежать?

Исходя из следующего вопроса, заданного несколько дней назад в 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 a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he a '{0}'", niceFoo.GetType().ToString());
    Console.ReadLine();
}

Конечно, badFoo выводит ошибочный Type.

Теперь я не знаю, имеет ли это какие-либо серьезные последствия, основанные на Эрике, описывающем это поведение как "невероятно опасную функцию", но может ли этот шаблон создать надежную угрозу?

Ответ 1

Хороший вопрос! То, как я это вижу, вы могли бы действительно ввести в заблуждение другого разработчика, если 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

Итак, yikes, вы успешно солгали, верно? Ну, да и нет... Учтите, что использование этого в качестве эксплойта означало бы использование вашего экземпляра 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 выше, чтобы создать хаос в библиотеках, написанных кем-то еще.

Ответ 2

Есть два способа убедиться в типе:

  • Используйте typeof для типа, который нельзя перегрузить

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he 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();
    
  • Передайте экземпляр в object и вызовите GetType() Method

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he 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();
    

Ответ 3

Нет, вы не можете заставить GetType lie. Вы вводите новый метод. Только код, который знает об этом методе, вызовет его.

Вы не можете, например, заставить сторонний или фрейм-код вызывать новый метод GetType вместо реального, так как этот код не знает, что ваш метод существует и поэтому никогда не будет его называть.

Однако вы можете путать своих разработчиков с такой декларацией. Любой код, который скомпилирован с вашим объявлением и который использует параметры или переменные, набранные как IFoo или любой тип, полученный из этого, действительно использует ваш новый метод. Но поскольку это влияет только на ваш собственный код, он на самом деле не навязывает "угрозу".

Если вы хотите предоставить собственное описание типа для класса, это должно быть сделано с помощью Custom Type Descriptor, возможно, путем аннотирования вашего класса с TypeDescriptionProviderAttribute. Это может быть полезно в некоторых ситуациях.

Ответ 4

Ну, на самом деле уже существует тип, который может находиться в GetType: любой тип с нулевым значением.

Этот код:

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

выводит True.


Собственно, это не те int?, который лежит, просто неявный приведение к object превращает int? в коробку int. Но тем не менее вы не можете сказать int? от int с помощью GetType().

Ответ 5

Я не думаю, что это произойдет, поскольку каждый библиотечный код, который вызывает GetType, объявит переменную как "Object" или как тип Generic '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 a '{1}'", actualName, instance.GetType());
    }

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

IFoo BadFoo говорит, что он "System.Int32"

IFoo NiceFoo говорит, что он "TypeConcept.NiceFoo"

Ответ 6

Хуже того, что может случиться, насколько я могу судить, вводит в заблуждение невинных программистов, которые, случается, используют отравленный класс, например:

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 - это экземпляр класса, который вы описываете в вопросе, он будет рассматриваться как неизвестный тип.

Итак, мой ответ - нет, не вижу реальной угрозы здесь.

Ответ 7

У вас есть несколько вариантов, если вы хотите играть в безопасности от такого взлома:

Перенесите объект сначала

Вы можете вызвать исходный метод GetType(), предварительно наведя экземпляр на object:

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

приводит к:

BadFoo says he a 'ConsoleApplication.BadFoo'

Использовать метод шаблона

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

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

GetType(badFoo);

Ответ 8

Существует разница между object.GetType и IFoo.GetType. GetType вызывается во время компиляции на неизвестных объектах, а не на интерфейсах. В вашем примере с выходом badFoo.GetType ожидается bahaviour, потому что вы перегружаете метод. Дело только в том, что другие программисты могут запутаться в этом поведении.

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

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

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