GetType () pode mentir?

94

Com base na seguinte pergunta feita há poucos dias no SO: GetType () e polimorfismo e lendo a resposta de Eric Lippert , comecei a pensar se fazer GetType()não ser virtual realmente garante que um objeto não possa mentir sobre ele Type.

Especificamente, a resposta de Eric afirma o seguinte:

Os designers do framework não irão adicionar um recurso incrivelmente perigoso, como permitir que um objeto minta sobre seu tipo apenas para torná-lo consistente com três outros métodos do mesmo tipo.

Agora a pergunta é: Eu posso fazer um objeto que faz mentira sobre seu tipo, sem que seja imediatamente óbvio? Posso estar profundamente errado aqui e adoraria um esclarecimento se for esse o caso, mas considere o seguinte código:

public interface IFoo
{
    Type GetType();
}

E as seguintes duas implementações da referida interface:

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

public class NiceFoo : IFoo
{
}

Então, se você executar o seguinte programa simples:

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

Com certeza badFooproduz um erro Type.

Agora, eu não sei se isso tem alguma implicação séria com base na descrição de Eric desse comportamento como um " recurso incrivelmente perigoso ", mas esse padrão poderia representar uma ameaça confiável?

Entre
fonte
3
título e tópico interessantes!
David
43
IFoo.GetTypee object.GetTypenão é a mesma coisa, então nada de ruim está acontecendo aqui, exceto um estilo ruim. Editar: Geralmente GetTypeserá chamado em algum objeto desconhecido em tempo de compilação, na maioria dos casos, objecte não em alguma interface duvidosa. :)
leppie
4
Seu título, senhor, fez meu dia.
Soner Gönül
5
Basta introduzir novos membros também chamados GetType, com a mesma assinatura. Isso não se relaciona com o GetTypemétodo que é importante. Você também pode criar um método de instância público que oculte o GetTypemétodo relevante , usando a newpalavra-chave modificadora. Observe, se você tiver um método genérico como static Type Test<T>(T t) { return t.GetType(); }(sem restrição T), então coisas como Test<IFoo>(new BadFoo())ainda chamarão o GetTypemétodo original .
Jeppe Stig Nielsen
2
@Jamiec - A questão "esse padrão pode representar uma ameaça confiável?" não é retórico.
Martin Smith

Respostas:

45

Boa pergunta! A meu ver, você só poderia realmente enganar um colega desenvolvedor se GetType fosse virtual no objeto, o que não é.

O que você fez é semelhante a sombreado GetType, assim:

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

com esta classe (e usando o código de amostra do MSDN para o método GetType () ) você realmente poderia ter:

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

então, caramba, você mentiu com sucesso, certo? Bem, sim e não ... Considere que usar isso como um exploit significaria usar sua instância BadFoo como um argumento para um método em algum lugar, que espera provavelmente um objectou um tipo de base comum para uma hierarquia de objetos. Algo assim:

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

mas CheckIfInt(foo)imprime "não é um int".

Então, basicamente (de volta ao seu exemplo), você só poderia realmente explorar seu "tipo mentiroso" com código que alguém escreveu em sua IFoointerface, o que é muito explícito sobre o fato de ter um GetType()método "personalizado" .

Apenas se GetType () fosse virtual no objeto, você seria capaz de criar um tipo "mentiroso" que poderia ser usado com métodos como o CheckIfIntacima para criar confusão em bibliotecas escritas por outra pessoa.

Paolo Falabella
fonte
sim, é exatamente o mesmo que sombreamento. O último parágrafo é o que realmente torna óbvio que realmente não há ameaça. Obrigado!
Entre
32

Existem duas maneiras de ter certeza sobre o tipo:

  1. Use typeofno tipo que não pode ser sobrecarregado

    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. Lance a instância para um objecte chame o GetType()Método

    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();
    
Johannes Wanzek
fonte
1
Como você vai usar typeofem um método que só obtém um IFoo badFoocomo parâmetro?
huysentruitw
typeofnão pode ser aplicado a instâncias de uma classe que é o que precisamos fazer aqui. Sua única opção é GetType().
Entre
Suas duas amostras estão fazendo duas coisas diferentes. As segundas linhas não respondem à pergunta exigida - explicitamente "obtêm o tipo BadFoo", mas não "obtêm o tipo de variável badFoo".
Dan Puzey
Sim, desculpe. Como de costume, não li a pergunta com atenção suficiente. Atualizei minha resposta para apontar as duas maneiras diferentes de ter certeza sobre o tipo.
Johannes Wanzek
1
Isso é o que eu estava apontando. Então, qual é o objetivo do seu comentário? :)
Johannes Wanzek
10

Não, você não pode fazer GetType mentir. Você está apenas introduzindo um novo método. Apenas o código que conhece esse método o chamará.

Você não pode, por exemplo, fazer com que o código de terceiros ou de estrutura chame seu novo método GetType em vez do real, uma vez que esse código não sabe que seu método existe e, portanto, nunca o chamará.

No entanto, você pode confundir seus próprios desenvolvedores com tal declaração. Qualquer código que seja compilado com sua declaração e que use parâmetros ou variáveis ​​digitadas como IFoo ou qualquer tipo derivado disso, de fato, usará seu novo método. Mas, uma vez que isso afeta apenas o seu próprio código, não impõe realmente uma "ameaça".

Se você deseja fornecer uma descrição de tipo personalizado para uma classe, isso deve ser feito usando um Descritor de Tipo Personalizado , talvez anotando sua classe com um TypeDescriptionProviderAttribute . Isso pode ser útil em algumas situações.

Mårten Wikström
fonte
2
+1 para o seu segundo parágrafo apontando explicitamente que o código de terceiros não conhece uma implementação GetType personalizada. Outras respostas sugeriram essa ideia, mas realmente não vieram e disseram (pelo menos não tão claramente).
brichins
7

Bem, na verdade não é já um tipo que pode estar em GetType: qualquer tipo anulável.

Este código :

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

saídas True.


Na verdade, não int?é quem está mentindo, apenas o elenco implícito se objecttransforma int?em um boxe int. Mas, no entanto, você não pode dizer int?a partir de intcom GetType().

Vlad
fonte
1
Qual é o comportamento esperado (ou pelo menos bem conhecido). As respostas a esta pergunta explicam este conceito de forma bastante clara, bem como a razão para isso.
brichins
@brichins: Bem, eu concordo que é conhecido, mas não posso concordar que seja bem conhecido. De qualquer forma, este é um caso em que GetType()produz um resultado um tanto estranho. Na verdade, perguntei a vários colegas se um não sombreado GetType()pode retornar algo que difere do tipo de tempo de execução do objeto real. A resposta de todos foi 'não'.
Vlad
5

Eu não acho que vá, já que todo código de biblioteca que chama GetType irá declarar a variável como 'Objeto' ou como um tipo genérico 'T'

O seguinte código:

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

estampas:

O objeto BadFoo diz que ele é um 'TypeConcept.BadFoo'

O objeto NiceFoo diz que é um 'TypeConcept.NiceFoo'

O tipo genérico BadFoo diz que ele é um 'TypeConcept.BadFoo'

O tipo genérico NiceFoo diz que ele é um 'TypeConcept.NiceFoo'

A única vez que este tipo de código resultará em cenários ruins é em seu próprio código, onde você declara o tipo de parâmetro como 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 diz que ele é um 'System.Int32'

IFoo NiceFoo diz que é um 'TypeConcept.NiceFoo'

Moeri
fonte
4

O pior que pode acontecer, tanto quanto posso dizer, é enganar programadores inocentes que usam a classe envenenada, por exemplo:

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 myInstancefor uma instância de uma classe como a que você descreve na pergunta, ela será tratada apenas como tipo desconhecido.

Portanto, minha resposta é não, não consigo ver nenhuma ameaça real aqui.

Shadow Wizard é ouvido para você
fonte
1
Certo. Um programador cuidadoso pode ver em tempo de compilação qual método "GetType" ele invoca. Object.GetType()é distinto de SomeUserdefinedInterfaceClassOrStruct.GetType(). Só que, se você usar o dynamictipo, nunca saberá o que acontecerá no momento da vinculação. Portanto, você deve usar dynamic x = expression; ... Type t = ((object)x).GetType();em casos como esse.
Jeppe Stig Nielsen
@Jeppe pontos justos! Eu acho que justifica uma resposta separada, minha resposta se concentra mais no programador "inocente" que não será tão cuidadoso.
Shadow Wizard is Ear For You
3

Você tem algumas opções se quiser jogar pelo seguro contra esse tipo de hack:

Lançar para o objeto primeiro

Você pode chamar o GetType()método original primeiro convertendo a instância para um object:

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

resulta em:

BadFoo says he's a 'ConsoleApplication.BadFoo'

Usar método de modelo

Usar este método de modelo também fornecerá o tipo real:

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

GetType(badFoo);
Huysentruitw
fonte
2

Existe uma diferença entre object.GetTypee IFoo.GetType. GetTypeé chamado em tempo de compilação em objetos desconhecidos e não em interfaces. No seu exemplo, com saída badFoo.GetTypeé esperado bahaviour, porque você sobrecarrega o método. A única coisa é que outros programadores podem se confundir com esse comportamento.

Mas se você usá- typeof()lo, o tipo será o mesmo e você não poderá sobrescrever typeof().

Além disso, o programador pode ver em tempo de compilação, qual método GetTypeele invoca.

Então, para sua pergunta: este padrão não pode representar uma ameaça confiável, mas também não é o melhor estilo de codificação.

bpoiss
fonte
badFoo.GetType()É um comportamento esperado, porque GetTypeestava sobrecarregado.
huysentruitw