GetType () puede mentir?

94

Basado en la siguiente pregunta hecha hace unos días en SO: GetType () y polimorfismo y leyendo la respuesta de Eric Lippert , comencé a pensar si hacer GetType()no ser virtual realmente garantizaba que un objeto no pudiera mentir sobre su Type.

Específicamente, la respuesta de Eric dice lo siguiente:

Los diseñadores de frameworks no van a agregar una característica increíblemente peligrosa como permitir que un objeto mienta sobre su tipo simplemente para que sea consistente con otros tres métodos del mismo tipo.

Ahora la pregunta es: ¿Puedo hacer un objeto que hace mentira acerca de su tipo, sin que sea inmediatamente obvio? Puede que esté profundamente equivocado aquí y me encantaría una aclaración si ese es el caso, pero considere el siguiente código:

public interface IFoo
{
    Type GetType();
}

Y las siguientes dos implementaciones de dicha interfaz:

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

public class NiceFoo : IFoo
{
}

Entonces, si ejecuta el siguiente programa simple:

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

Efectivamente, badFoogenera un error Type.

Ahora bien, no sé si esto tiene implicaciones serias basándome en que Eric describió este comportamiento como una " característica increíblemente peligrosa ", pero ¿podría este patrón representar una amenaza creíble?

Entre
fuente
3
interesante título y tema!
David
43
IFoo.GetTypey object.GetTypeno es lo mismo, así que aquí no pasa nada malo excepto el estilo pobre. Editar: generalmente GetTypese llamará a algún objeto desconocido en tiempo de compilación, en la mayoría de los casos objecty no a una interfaz poco fiable. :)
leppie
4
Su título señor, me alegró el día.
Soner Gönül
5
Simplemente presenta a los nuevos miembros también llamados GetType, con la misma firma. Eso no se relaciona con el GetTypemétodo que es importante. También puede crear un método de instancia pública que oculte el GetTypemétodo relevante , utilizando la newpalabra clave modificadora. Tenga en cuenta que si tiene un método genérico como static Type Test<T>(T t) { return t.GetType(); }(sin restricciones T), entonces cosas como Test<IFoo>(new BadFoo())seguirán llamando al GetTypemétodo original .
Jeppe Stig Nielsen
2
@Jamiec - La pregunta "¿podría este patrón representar una amenaza creíble?" no es retórico.
Martin Smith

Respuestas:

45

¡Buena pregunta! A mi modo de ver, solo se podría engañar a un compañero desarrollador si GetType fuera virtual en el objeto, que no lo es.

Lo que hizo es similar a sombrear GetType, así:

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

con esta clase (y usando el código de muestra de MSDN para el método GetType () ) podría tener:

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

entonces, ¡ay, has mentido con éxito, verdad? Bueno, sí y no ... Considere que usar esto como un exploit significaría usar su instancia BadFoo como un argumento para un método en algún lugar, que espera probablemente un objecttipo base común o uno para una jerarquía de objetos. Algo como esto:

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

pero CheckIfInt(foo)imprime "no un int".

Entonces, básicamente (volviendo a su ejemplo), en realidad solo podría explotar su "tipo mentiroso" con el código que alguien escribió en su IFoointerfaz, que es muy explícito sobre el hecho de que tiene un GetType()método "personalizado" .

Solo si GetType () fuera virtual en el objeto, podría crear un tipo "mentiroso" que podría usarse con métodos como el CheckIfIntanterior para crear estragos en las bibliotecas escritas por otra persona.

Paolo Falabella
fuente
sí, es exactamente lo mismo que sombrear. El último párrafo es lo que realmente hace evidente que realmente no existe ninguna amenaza. ¡Gracias!
Entre
32

Hay dos formas de estar seguro sobre el tipo:

  1. Usar typeofen el tipo que no se puede sobrecargar

    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. Transmita la instancia a un objecty llame al 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
fuente
1
¿Cómo lo usará typeofen un método que solo obtiene un IFoo badFooparámetro como?
huysentruitw
typeofno se puede aplicar a instancias de una clase, que es lo que debemos hacer aquí. Tu única opción es GetType().
Entre
Tus dos muestras están haciendo dos cosas diferentes. Las segundas líneas no responden a la pregunta requerida: explícitamente "obtienen el tipo BadFoo" pero no "obtienen el tipo de variable badFoo".
Dan Puzey
Si lo siento Como de costumbre, no leí la pregunta con suficiente atención. Actualicé mi respuesta para señalar las dos formas diferentes de estar seguro sobre el tipo.
Johannes Wanzek
1
Esto es lo que estaba señalando. Entonces, ¿cuál es el punto de tu comentario? :)
Johannes Wanzek
10

No, no puedes hacer que GetType mienta. Solo está introduciendo un nuevo método. Solo el código que conoce este método lo llamará.

Por ejemplo, no puede hacer que un código de marco o de terceros llame a su nuevo método GetType en lugar del real, ya que ese código no sabe que su método existe y, por lo tanto, nunca lo llamará.

Sin embargo, puede confundir a sus propios desarrolladores con tal declaración. Cualquier código que se compile con su declaración y que use parámetros o variables escritos como IFoo o cualquier tipo derivado de eso usará su nuevo método en su lugar. Pero dado que eso solo afecta a su propio código, realmente no impone una "amenaza".

Si desea proporcionar una descripción de tipo personalizado para una clase esto debe hacerse utilizando un descriptor de tipo personalizado , tal vez mediante la anotación de su clase con un TypeDescriptionProviderAttribute . Esto puede resultar útil en algunas situaciones.

Mårten Wikström
fuente
2
+1 para su segundo párrafo que señala explícitamente que el código de terceros no conoce una implementación GetType personalizada. Otras respuestas insinuaron esa idea, pero realmente no salieron y la dijeron (al menos no con tanta claridad).
brichins
7

Bueno, en realidad ya existe un tipo que puede estar en GetType: cualquier tipo anulable.

Este código :

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

salidas True.


En realidad, no int?es quién miente, solo el elenco implícito se objectconvierte int?en una caja int. Sin embargo, no se puede distinguir int?desde el intprincipio GetType().

Vlad
fuente
1
Cuál es el comportamiento esperado (o al menos conocido). Las respuestas a esta pregunta explican este concepto con bastante claridad, así como el motivo.
brichins
@brichins: Bueno, estoy de acuerdo en que se conoce, pero no puedo estar de acuerdo en que sea bien conocido. De todos modos, este es un caso en el que GetType()produce un resultado algo extraño. De hecho, le pregunté a varios colegas si un no sombreado GetType()puede devolver algo que difiera del tipo de tiempo de ejecución del objeto real, la respuesta de todos fue 'no'.
Vlad
5

No creo que lo haga, ya que cada código de biblioteca que llame a GetType declarará la variable como 'Objeto' o como un tipo genérico 'T'

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

huellas dactilares:

Object BadFoo dice que es un 'TypeConcept.BadFoo'

Object NiceFoo dice que es un 'TypeConcept.NiceFoo'

Tipo genérico BadFoo dice que es un 'TypeConcept.BadFoo'

El tipo genérico NiceFoo dice que es un 'TypeConcept.NiceFoo'

La única vez que este tipo de código dará como resultado un mal escenario es en su propio código, donde declara el 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 dice que es un 'System.Int32'

IFoo NiceFoo dice que es un 'TypeConcept.NiceFoo'

Moeri
fuente
4

Lo peor que puede suceder hasta donde yo sé es engañar a los programadores inocentes que utilizan la clase envenenada, por ejemplo:

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

Si myInstancees una instancia de una clase como la que describe en la pregunta, simplemente se tratará como de tipo desconocido.

Entonces mi respuesta es no, no veo ninguna amenaza real aquí.

Shadow Wizard es el oído para ti
fuente
1
Por supuesto. Un programador cuidadoso puede ver en tiempo de compilación qué método "GetType" invoca. Object.GetType()es distinto de SomeUserdefinedInterfaceClassOrStruct.GetType(). Solo que, si usa el dynamictipo, nunca podrá saber qué sucederá en el momento de la vinculación. Entonces deberías usar dynamic x = expression; ... Type t = ((object)x).GetType();en casos como ese.
Jeppe Stig Nielsen
¡Puntos justos de @Jeppe! Creo que justifica una respuesta separada, mi respuesta se centra más en un programador "inocente" que no será tan cuidadoso.
Shadow Wizard es Ear For You
3

Tienes algunas opciones si quieres jugar seguro contra ese tipo de piratería:

Enviar al objeto primero

Puede llamar al GetType()método original al convertir primero la instancia en un object:

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

resulta en:

BadFoo says he's a 'ConsoleApplication.BadFoo'

Usar método de plantilla

El uso de este método de plantilla también le dará el tipo real:

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

GetType(badFoo);
huysentruitw
fuente
2

Hay una diferencia entre object.GetTypey IFoo.GetType. GetTypese llama en tiempo de compilación en objetos no conocidos, no en interfaces. En su ejemplo, con la salida badFoo.GetTypese espera un comportamiento, porque sobrecarga el método. Lo único es que otros programadores pueden confundirse con este comportamiento.

Pero si lo usa typeof(), dará como resultado que el tipo es el mismo y no puede sobrescribir typeof().

Además, el programador puede ver en tiempo de compilación qué método GetTypeinvoca.

Entonces, a su pregunta: este patrón no puede representar una amenaza creíble, pero tampoco es el mejor estilo de codificación.

bpoiss
fuente
badFoo.GetType()ES comportamiento esperado, porque GetTypeestaba sobrecargado.
huysentruitw