C# 6 中的字符串插值和重写的 ToString()

String interpolation in C# 6 and overridden 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("joe@bloggs.com");

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

return 类型名称的字符串 (Namespace.EmailAddress),而不是覆盖的 ToString 方法 (joe@bloggs.com)?

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

其实$""最终都是使用了classStringBuilder中的一个内部方法。

StringBuilder.AppendFormatHelper(IFormatProvider 提供程序、字符串格式、ParamsArray 参数)

关键代码:

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

插值按预期工作,因为您的 类 没有 覆盖 Object.ToString()ValueBase 定义了一个 new 方法 隐藏 Object.ToString 而不是覆盖它。

只需从 ValueBase 中删除 ToString。在这种情况下,Email.Address 将正确覆盖 Object.ToString,插值将 return 获得所需的结果。

具体来说,将 ValueBase 更改为:

public abstract class ValueBase
{
}

制作测试代码

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

return joe@bloggs.com

更新

正如人们建议的那样,可以添加基础 ToString() 方法以强制实施者在其 类 中实施自定义 ToString 方法。这可以通过定义 abstract override 方法来实现。

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

嗯,$"{email}" 字符串自动转换为 string.Format("{0}", email) 并且此方法的第二个参数是 params object[] 类型。因此它在调用 ToString() 方法之前将所有值向上转换为 object 。在您的代码中,您只需将此方法替换为 ValueBase class 中的新方法,并且 EmailAddress class 中的 override 关键字实现此抽象方法而不是原始对象的一.

如果将第二个值显式转换为对象,则可以轻松地对其进行测试:

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

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

正如您现在看到的那样 emailString2 returns typename 也是如此。您可以从抽象 class 中删除 ToString() 方法,让 EmailAdress class 实现对象的 ToString() 或在抽象 class 中实现它。例如:

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

使用这个新代码,输出符合预期:

joe@bloggs.com
joe@bloggs.com

首先,躲ToString是自讨苦吃,千万别做; string.Format("{0}", email)$"{email} 打印出的内容与 email.ToString().

不同,这很令人困惑

其次,emailString1emailString2不可能和你说的一样。 email.ToString()的输出是"joe@bloggs.com"$"{email}"的输出是"{namespace}.EmailAddress".

为什么不同?这是因为你隐藏ToStringValueBaseEmailAddress.ToString 类型的调用(通过 EmailAddress 类型引用的调用)将调用 ToString 的新实现,但 Object.ToString 调用(通过 object typed reference) 将调用 Object 中的实现; $"{email}" 等同于 string.Format("{0}", email) 在道德上等同于 string.Format("{0}", (object)mail) 所以你实际上是在调用 ToString 形式的对象类型引用;

您需要做的是 覆盖 ToString 并且您将获得预期的行为:

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

任何对 EmailAddress.ToString 的调用,无论引用的类型如何,都将调用覆盖的实现。

请注意,您似乎将 newoverride 混为一谈:

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

您没有覆盖 ToString,而是隐藏它。要查看 newoverride 之间的区别,请阅读 this SO question