Строковая интерполяция в С# 6 и переопределенная ToString()

Учитывая следующие классы:

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

Почему:

var email = new EmailAddress("[email protected]");

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

возвращает строку имени типа (Namespace.EmailAddress), а не переопределенный метод ToString ([email protected])?

Ответ 1

Ну, строка $"{email}" автоматически преобразуется в string.Format("{0}", email), а второй метод этого метода имеет тип params object[]. Поэтому перед вызовом метода ToString() он поднимает все значения до object. В коде вы просто заменяете этот метод новым внутри класса ValueBase, а ключевое слово override внутри класса EmailAddress реализует этот абстрактный метод, а не исходный объект.

Вы можете легко протестировать его, если явно указать свое второе значение для объекта:

var email = new EmailAddress("[email protected]");

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

Как вы теперь видите, emailString2 возвращает также имя_файла. Вы можете удалить метод ToString() из абстрактного класса и позволить классу EmailAdress реализовать объект ToString() или реализовать его там в абстрактном классе. Например:

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

    public class EmailAddress : ValueBase
    {
       ...

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

С помощью этого нового кода вывод будет таким, как ожидалось:

[email protected]
[email protected]

Ответ 2

Интерполяция работает так, как ожидалось, так как ваши классы не переопределяют Object.ToString(). ValueBase определяет новый метод, который скрывает Object.ToString вместо его переопределения.

Просто удалите ToString из ValueBase. В этом случае Email.Address правильно вернет Object.ToString, и интерполяция вернет желаемый результат.

В частности, меняя ValueBase на это:

public abstract class ValueBase
{
}

Делает тестовый код

var email = new EmailAddress("[email protected]");
string emailString1 = $"{email}";

return [email protected]

UPDATE

Как предложили люди, базовый метод ToString() может быть добавлен, чтобы заставить исполнителей реализовать собственный метод ToString в своих классах. Это может быть достигнуто путем определения метода abstract override.

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

Ответ 3

string emailString1 = $"{email}"; //call the Object.ToString() method
string emailString2 = email.ToString();//call your overrided ValueBase.ToString() method

На самом деле $"" В конечном итоге использует внутренний метод в классе StringBuilder.

StringBuilder.AppendFormatHelper(поставщик IFormatProvider, строковый формат, аргументы ParamsArray)

key code:

 object obj = args[index3];
 //... bilibili About On Line 1590
  else if (obj != null)
    str = obj.ToString();// So it use the object.ToString()

Ответ 4

Прежде всего, скрытие ToString требует неприятностей, не делайте этого; это довольно запутанно, что string.Format("{0}", email) или $"{email} выводит что-то отличное от email.ToString().

Во-вторых, emailString1 и emailString2 не могут быть такими же, как вы утверждаете. Выходной сигнал email.ToString() равен "[email protected]", вывод $"{email}" равен "{namespace}.EmailAddress".

Почему разница? Это потому, что вы скрываете ToString в ValueBase. Вызов типа EmailAddress.ToString (вызов с помощью EmailAddress типизированной ссылки) вызовет новую реализацию ToString, но вызов Object.ToString (вызов с помощью object типизированной ссылки) вызовет реализацию в object; $"{email}" эквивалентен string.Format("{0}", email), который морально эквивалентен string.Format("{0}", (object)mail), поэтому вы действительно вызываете ToString форму ссылочного типа объекта;

Что вам нужно сделать, это переопределить ToString, и вы получите ожидаемое поведение:

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

Любой вызов EmailAddress.ToString, no mater, тип ссылки вызовет переопределенную реализацию.

Обратите внимание, что вы, кажется, объединяете новые и переопределяете:

Почему... возвращает строку имени типа (Namespace.EmailAddress), а не переопределенный метод ToString ([email protected])?

Вы не переопределяете ToString, вы его скрываете. Чтобы увидеть разницу между new и override, прочитайте этот вопрос SO.