Quiz of the month: Type.FullName


I once had a dev manager who loves puzzles and quizes. During our team meetings, he will write some code on the board and the team would have a competition to see who can spot the mistake first or who can answer a question about the code first. I always enjoyed those quiz. So I think I am going to start this on my blog. Once a month, I’ll find something interesting and see who can give me the answer and an explaination why. 🙂 Of course you can just run the code and have the right answers, but understanding why is the interesting part.


Here is my first “Quiz of the month”! I’ll post the answer to this in 2 weeks.


Without running it, can you tell me whether the code below will assert? If yes, why? If not, why not?


    class Prog


    {


        static void Main(string[] args)


        {


            Type t1 = typeof(DerivedClass<>);


            Type t2 = t1.BaseType;


            string fullName1 = t1.FullName;


            string fullName2 = t2.FullName;


            Debug.Assert(fullName1 == fullName2);


        }


    }


 


    public class BaseClass<T>


    {


    }


 


    public class DerivedClass<T> : BaseClass<T>


    {


    }

Comments (10)

  1. I certainly look like it should.

    t1.FullName should return "DerivedClass" and t2.FulleName should return "BaseClass"

  2. jaredpar says:

    If you want some real fun open this up in Visual Studio and break on the Assert line.  To spoil the experience ,t2 will come up as null in the locals window.  But the kick is that it’s not actually null.  Probably should file a bug on that.

  3. Barry Kelly says:

    I’m not sure, Kathy, but just looking at that code it looks like there is a mistake, because there are one big reason why the assert wouldn’t fire. t1 refers to something related to DerivedClass, while t2 refers to something related to BaseClass, so their FullNames couldn’t be equal.

    The mistake, I think, is that you should have had code like this in Main:

               Type t1 = typeof(DerivedClass<>).BaseType;
               Type t2 = typeof(BaseClass<>);
               string fullName1 = t1.FullName;
               string fullName2 = t2.FullName;
               Debug.Assert(fullName1 == fullName2);

    This makes the question more interesting. Is the generic base type of a generic type the same as its generic base type’s type? The answer is no.

    t1 refers to the base type of DerivedClass<>. However, DerivedClass<> takes a generic argument T, and inherits from BaseType<> *specialized with this generic argument*. Thus, the base type of DerivedClass<> is *already specialized*, and is no longer an open generic type willing to accept a generic argument. Its generic argument is already filled by DerivedClass<>’s T.

    t2 refers to the BaseType<>, which is an open generic type, and is willing to be called by MakeGenericType(), providing a type argument.

    Thus, the two types, t1 and t2, are different, so we shouldn’t expect them to have the same FullNames.

    Type.FullName is expected to return a string that you can later pass to Type.GetType(), and retrieve the same type. That is, “Type.GetType(t.FullName) == t” should be true, where t is of type Type.

    It would appear that the CLR has no valid way of describing a generic type specialized by a generic type argument defined in a different class by way of inheritance. So, t1.FullName returns null, because Type.GetType() wouldn’t be able to return the BaseType<> specialized with DerivedType<>’s T. Of course, getting such a BaseType<> specialization wouldn’t be very useful, so that’s ok.

  4. davkean says:

    Jared,

    That’s probably not a bug, t2 could have been collected by then. Although, when compiling in debug that likely not to happen.

  5. I suggest a possible solution: The type full name MUST guarantee that we can create a type ( also a generic one ) calling Type.GetType(string..). If we look to the BaseType property documentation we can read: “If the current Type represents a type parameter of a generic type definition, BaseType returns the class constraint, that is, the class the type parameter must inherit. If there is no class constraint, BaseType returns System.Object”. In fact if we inspect t2 we will notice that it is not an IsGenericTypeDefinition=true but the “real” type BaseClass is. So we cannot create an instance  of t2 and this is the reason t2 will have a fullName value null that cause the program to assert.
    Please apologize me for my bad english.

  6. Greg D says:

    I’ll take a stab at it.  Let’s see.

    t1 should (in string representation) be:

    “DerivedClass`1[T]”

    per the C# spec.  What is (in my opinion) intuitive behavior for t1.BaseType would then yield:

    “BaseClass`1[T]”

    I’m going to run with the intuitive behavior b/c I didn’t spot anything in the docs calling out special-case behavior for taking the BaseType of an unbound generic type.

    I then expect fullName1 and fullName2 to be both defined and not-equal, resulting in a false assertion clause and an assertion fire.

    Now I’m going to actually run it and find out if I’m correct, though I won’t post whether or not I am.  I don’t want to spoil it for anyone else who happens to be reading the comments, after all!

  7. Jeff Parker says:

    Well, Hmm, this one is a little different.

    t1 should give the name of the derived class. complete with it’s namespace.

    However I would expect t2 would be the string to the base type of t1 which is a Type Class hence doesn’t have a type so it is null.

    I confirmed this by writing a little test code.

               Type t1 = typeof(DerivedClass<>);

               Type t2 = t1.BaseType;

               if (t2.FullName == null) Console.WriteLine ("Its null");

               string fullName1 = t1.FullName;

               string fullName2 = t2.FullName;

  8. mihailik says:

    It is strange, that t2.FullName is null, but t2.ToString() returns "BaseClass`1[T]"