GetType() can lie?

Based on the following question asked a few days ago in SO: GetType() and polymorphism and reading Eric Lippert's answer, I started thinking if making GetType() not be virtual really ensured that an object could not lie about its Type.

Specifically, Eric's answer states the following:

The framework designers are not going to add an incredibly dangerous feature such as allowing an object to lie about its type merely to make it consistent with three other methods on the same type.

Now the question is: can I make an object that does lie about its type without it being immediately obvious? I may be profoundly wrong here and I'd love clarification if that is the case, but consider the following code:

public interface IFoo
{
    Type GetType();
}

And the following two implementations of said interface:

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

public class NiceFoo : IFoo
{
}

Then if you run the following simple program:

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

Sure enough badFoo outputs an erroneous Type.

Now I don't know if this has any serious implications based on Eric describing this behavior as an "incredibly dangerous feature", but could this pattern pose a credible threat?


Solution 1:

Nice question! The way I see it, you could only really mislead a fellow developer if GetType was virtual on object, which it isn't.

What you did is akin to shadowing GetType, like this:

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

with this class (and using the sample code from the MSDN for the GetType() method) you could indeed have:

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

so, yikes, you've successfully lied, right? Well, yes and no... Consider that using this as an exploit would mean using your BadFoo instance as an argument to a method somewhere, that expects likely an object or a common base type for a hierarchy of objects. Something like this:

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

but CheckIfInt(foo) prints "not an int".

So, basically (back to your example), you could really only exploit your "lying type" with code that someone wrote against your IFoo interface, which is very explicit about the fact that it has a "custom" GetType() method.

Only if GetType() was virtual on object you would be able to craft a "lying" type that could be used with methods like CheckIfInt above to create havoc in libraries written by someone else.

Solution 2:

There are two ways of being sure about the Type:

  1. Use typeof on the Type which can't be overloaded

    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. Cast the instance to an object and call the GetType() Method

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

Solution 3:

No, you can't make GetType lie. You are only introducing a new method. Only code that are aware of this method will call it.

You cannot for example make third-party or framework code call your new GetType method instead of the real one, since that code does not know that your method exists and will therefore never call it.

You can however confuse your own developers with such a declaration. Any code that is compiled with your declaration and that use parameters or variables typed as IFoo or any type derived from that will indeed use your new method instead. But since that only affects your own code it does not really impose a "threat".

If you do want to provide a custom type description for a class this should be done using a Custom Type Descriptor, perhaps by annotating your class with a TypeDescriptionProviderAttribute. This can be useful in some situations.

Solution 4:

Well, actually there is already a type which can lie in GetType: any nullable type.

This code:

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

outputs True.


Actually, it's not the int? who is lying, just implicit cast to object turns int? into a boxed int. But nevertheless you cannot tell int? from int with GetType().

Solution 5:

I don't think it will, since every library code that calls GetType will declare the variable as 'Object' or as a Generic type 'T'

The following code:

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

prints:

Object BadFoo says he's a 'TypeConcept.BadFoo'

Object NiceFoo says he's a 'TypeConcept.NiceFoo'

Generic Type BadFoo says he's a 'TypeConcept.BadFoo'

Generic Type NiceFoo says he's a 'TypeConcept.NiceFoo'

The only time this kind of code will result in bad scenario's is in your own code, where you declare the parameter type as 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 says he's a 'System.Int32'

IFoo NiceFoo says he's a 'TypeConcept.NiceFoo'