Even More Conversion Trivia

Even More Conversion Trivia

  • Comments 14

I learn something new about C# every day. This is a subtle and tricky language.

Pop quiz: foo is of a type which has a base class which has made a normal, everyday virtual override of System.Object.ToString. There are no additional overrides, hides, etc of ToString anywhere in foo's inheritance hierarchy. No ToString extension methods, etc. Under what circumstances do foo.ToString() and ((object)foo).ToString() produce different results? Remember, it is a virtual function, so the cast should not make a difference in what function is actually called.

As it turns out, my mistaken belief that the two of these have the same semantics caused a bug in the part of the compiler where we translate an expression tree lambda into a call to the Lambda<T> factory. That in turn was masking an even more subtle bug in the dynamic expression tree compiler.

Anyone out there have a guess as to what type and value of foo produces different behaviour here? I'll post the answer next time along with some discussion of the codegen issue.

  • How about if foo is a Random?

    Or if ToString() returns the ToString() of the current system time?

    :)

  • A hide does not qualify as an override does it?

    public class A {

    public override string ToString() {

    return "A";

    }

    }

    public class B : A {

    public new string ToString() {

    return "B";

    }

    }

  • Hmm I've been thinking along the same lines as Ajai i.e. if you hide the override then the il is different, so this code:

    System.Console.WriteLine(p.ToString());

    System.Console.WriteLine(((object)p).ToString());

    compiles to:

     IL_0007:  ldloc.0

     IL_0008:  callvirt   instance string [foo]foo::ToString()

     IL_000d:  call       void [mscorlib]System.Console::WriteLine(string)

     IL_0013:  ldloc.0

     IL_0014:  callvirt   instance string [mscorlib]System.Object::ToString()

     IL_0019:  call       void [mscorlib]System.Console::WriteLine(string)

    Which if you recompile foo changing the new to an override leaves the caller being subtly incorrect, but I can't help thinking this isn't the right answer.

    I'll keep thinking on this tomorrow while I try and get rid of a nasty NC_ACTIVATE flicker in a VB6 program that marketing has decided to refresh.

  • Can one determine the pointer to value types (stack location) and reference types (heap location)?  If so, boxing would change the pointer...

  • ToString() does some reflection and prints the class name?

  • There are no additional overrides or hides, sorry, I should have been more clear.

  • Not sure this is what you mean, but it certainly exhibits the behavior you're descxribing:

       public interface IProblem

       {

           string ToString();

       }

       public class MyBase

       {

           public override string ToString()

           {

               return "This is the Base class";

           }

       }

       public class MyDerived : MyBase, IProblem

       {

           #region IProblem Members

           public string ToString()

           {

               return "Implementing this here interface";

           }

           #endregion

       }

       class Program

       {

           static void Main(string[] args)

           {

               MyDerived foo = new MyDerived();

               string s = foo.ToString();

               Console.WriteLine(s);

               string s2 = ((object)foo).ToString();

               Console.WriteLine(s2);

           }

       }

    Calling foo.ToString() calls the version defined in the IProblem interface. Casting to object clls the version defined in MyBase, because the interface does not override the ToString() method, but rather defines a new ToStrng() that is part of the MyDerived class, and part of the IProblem interface.

  • ValueType overrides ToString(). ToString() on nullable types is lifted to the inner type. ToString() on a null value of a nullable type results in an empty string; but a cast to 'object' results in a null reference, this being a change implemented in the CLR released with VS2005 Beta 2. Calling a virtual method on a null reference (or indeed, any method with callvirt) results in a null reference exception. Example code:

    ---8<---

    using System;

    enum Foo

    {

       Bar,

       Baz

    }

    class App

    {

       static void Main()

       {

           Foo? x = null;

           Catch(delegate

           {

               Console.WriteLine(x.ToString());

           });

           Catch(delegate

           {

               Console.WriteLine(((object) x).ToString());

           });

       }

       delegate void Code();

       static void Catch(Code code)

       {

           try

           {

               Console.WriteLine("Executing...");

               code();

               Console.WriteLine("Done.");

           }

           catch (Exception ex)

           {

               Console.WriteLine("Caught: {0}: {1}", ex.GetType().Name, ex.Message);

           }

       }

    }

    --->8---

    The code above uses an enum, but it can be rewritten with user-defined value type to eliminate all other overrides of ToString(), to stay within the original problem spec.

    The biggest clue was the conspicuous use of the word 'type' rather than 'class'.

  • Which version of CLR you use? It seems that in .net 2.0 CLR this can't happen.Maybe you have reintroduced this in CLR 3.5?

  • The biggest news in the C# community is the official announcement of Silverlight at the MIX conference.

  • I've not really thought this through (as in tried it in code); but what happens if you define an conversion operator to converting foo to object? You might not even have the same object anymore...

  • Defining a custom conversion to a base class is definitely odd... and not allowed apparently, so my previous post doesn't hold water.

  • You canh always redeclare a methods(also virtual ones) with the keyword new, making it "public new string ToString()"

  • A puzzle in c# not related directly to the puzzle given here but thought it time to sent one the other way :-)

    If T is an enumeration would that statement always be true?

    Enum.IsDefined(typeof(T), default(T))

    Well the answer is no, but when isn't it true?

    http://runefs.wordpress.com/2008/06/12/when-defaults-are-not-valid/

Page 1 of 1 (14 items)