Строковая интерполяция в С# 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.