17

Given the following classes:

public abstract class ValueBase
{
    public new abstract string ToString();
}

public class EmailAddress : ValueBase
{
    public MailAddress MailAddress { get; }

    public EmailAddress([NotNull] string address)
    {
        MailAddress = new MailAddress(address);
    }

    public override string ToString()
    {
        return MailAddress.Address;
    }
}

Why does:

var email = new EmailAddress("joe@bloggs.com");

string emailString1 = $"{email}";
string emailString2 = email.ToString();

return a string of the type name (Namespace.EmailAddress), not the overridden ToString method (joe@bloggs.com)?

Darbio
  • 11,286
  • 12
  • 60
  • 100

4 Answers4

25

Interpolation works as expected since your classes do not override Object.ToString(). ValueBase defines a new method that hides Object.ToString instead of overriding it.

Simply remove ToString from ValueBase. In this case Email.Address will override Object.ToString correctly, and interpolation will return the desired result.

Specifically, changing ValueBase to this:

public abstract class ValueBase
{
}

Makes the test code

var email = new EmailAddress("joe@bloggs.com");
string emailString1 = $"{email}";

return joe@bloggs.com

UPDATE

As people suggested, the base ToString() method could be added to force implementers to implement a custom ToString method in their classes. This can be achieved by defining an abstract override method.

public abstract class ValueBase
{
    public abstract override string ToString();
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • 5
    It seems the OP wants to enforce a custom implementation of the `ToString` method. The easiest way would be to override `ToString` to call some *other* abstract method. – Luaan Apr 13 '16 at 12:12
  • 5
    @Luaan nope, the easiest way would be `public abstract override string ToString();` – Lucas Trzesniewski Apr 13 '16 at 18:57
9

Well, $"{email}" string is automatically converted to string.Format("{0}", email) and this method's second parameter is of type params object[]. So it upcasts all values to object before invoking ToString() method. In your code you simply replace this method with new one inside ValueBase class, and override keyword inside EmailAddress class implements this abstract method and not the original object's one.

You can easily test it if you cast your second value to object explicitly :

var email = new EmailAddress("joe@bloggs.com");

string emailString1 = $"{email}";
string emailString2 = ((object)email).ToString();

As you can see now emailString2 returns typename as well. You can remove ToString() method from abstract class and let EmailAdress class to implement object's ToString() or implement it there in abstract class. For example :

    public abstract class ValueBase
    {
        // overrides object's ToString()
        public override string ToString()
        {
            return base.ToString();
        }
    }

    public class EmailAddress : ValueBase
    {
       ...

       // overrides ValueBase's ToString()
       public override string ToString()
       {
           return MailAddress.Address;
       }
    }

With this new code the output is as expected :

joe@bloggs.com
joe@bloggs.com
Fabjan
  • 13,506
  • 4
  • 25
  • 52
3
string emailString1 = $"{email}"; //call the Object.ToString() method
string emailString2 = email.ToString();//call your overrided ValueBase.ToString() method

Actually $"" Ultimately are using a internal method in the class StringBuilder.

StringBuilder.AppendFormatHelper(IFormatProvider provider, string format, ParamsArray args)

The key code:

 object obj = args[index3];
 //... bilibili About On Line 1590
  else if (obj != null)
    str = obj.ToString();// So it use the object.ToString()
svick
  • 236,525
  • 50
  • 385
  • 514
chsword
  • 2,032
  • 17
  • 24
2

First off, hiding ToString is asking for trouble, dont do it; it is pretty confusing that string.Format("{0}", email) or $"{email} prints out something different than email.ToString().

Second, emailString1 and emailString2 can not be the same as you claim. The output of email.ToString() is "joe@bloggs.com", the output of $"{email}" is "{namespace}.EmailAddress".

Why the difference? It's because you are hiding ToString in ValueBase. A call of the type EmailAddress.ToString (a call through an EmailAddress typed reference) will call the new implementation of ToString but a call Object.ToString (a call through an object typed reference) will call the implementation in Object; $"{email}" is equivalent to string.Format("{0}", email) which is morally equivalent to string.Format("{0}", (object)mail) so you are really invoking ToString form an object typed reference;

What you need to do is override ToString and you will get the expected behavior:

public abstract class ValueBase
{
    public override abstract string ToString();
}

Any call to an EmailAddress.ToString, no mater the type of the reference will call the overriden implementation.

Note that you seem to be conflating new and override:

Why does ... return a string of the type name (Namespace.EmailAddress), not the overridden ToString method (joe@bloggs.com)?

You are not overriding ToString, you are hiding it. To see the difference between new and override read this SO question.

Community
  • 1
  • 1
InBetween
  • 32,319
  • 3
  • 50
  • 90