Redundant 'object.tostring()' call 引发的思考

1. 概述

今天在写代码的过程都遇到了一个提示:

redundant 'object.tostring()' call

截图如下:

IDE的意思是可以去除 ToString() 的调用, 为什么会有这个提示呢?

查看 MakeCallInfo 这个类发现覆写了 ToString() 方法. 那么是不是只要有 ToString() 方法就会有这个提示呢?

2. 测试

2.1 不添加 ToString() 方法

编写如下代码:

namespace DevConsoleApp {
    class Program {
        static void Main(string[] args) {
            Person p = new Person();
            p.Name = "张三";
            p.Age = 13;
            Console.WriteLine(p);
            Console.WriteLine(p.ToString());
            Console.WriteLine($"this is {p}");
            Console.WriteLine($"this is {p.ToString()}");
            Console.ReadKey();
        }
    }
 
    class Person {
        public string Name;
        public int Age;
    }
}

Person 类没有继承覆写 ToString() 方法, 在 Main 方法中的四种写法中, IDE在最后的一种写法中做了提示:

因为 Person 类没有 ToString() 方法, 实际调用的是 object.ToString() 方法, 所以这里会提示.

运行结果如下:

2. 添加 ToString() 方法

修改代码如下:

namespace DevConsoleApp {
    class Program {
        static void Main(string[] args) {
            Person p = new Person();
            p.Name = "张三";
            p.Age = 13;
            Console.WriteLine(p);
            Console.WriteLine(p.ToString());
            Console.WriteLine($"this is {p}");
            Console.WriteLine($"this is {p.ToString()}");
            Console.ReadKey();
        }
    }
 
    class Person {
        public string Name;
        public int Age;
 
        public string ToString() {
            return $"{nameof(Name)}: {Name}, {nameof(Age)}: {Age}";
        }
    }
}

这种修改以后, IDE 在输出的时候没有再提示 redundant 'object.tostring()' call , 截图如下:

但是, 此时的 ToString() 方法会有提示:

因为新添加的方法和父类的方法同名, 所以这里会提示让你确认这是覆写的父类方法还是新增的方法.

如果不按照它的提示, 直接运行会怎样?

运行结果可以看出, 第一个输出不是 ToString() 的格式字符串, 这说明编译器默认该方法为 新方法, 和 添加 new 关键字一致.

2.3 添加覆写方法

将2.2 的 new 改为 override , 即覆写父类的 ToString() 方法:

namespace DevConsoleApp {
    class Program {
        static void Main(string[] args) {
            Person p = new Person();
            p.Name = "张三";
            p.Age = 13;
            Console.WriteLine(p);
            Console.WriteLine(p.ToString());
            Console.WriteLine($"this is {p}");
            Console.WriteLine($"this is {p.ToString()}");
            Console.ReadKey();
        }
    }
 
    class Person {
        public string Name;
        public int Age;
 
        public override string ToString() {
            return $"{nameof(Name)}: {Name}, {nameof(Age)}: {Age}";
        }
    }
}

此时, 输出部分 IDE 扔会提示 redundant 'object.tostring()' call , :

运行结果呢?

从输出结果看, 第一种和第二种输出结果是一致的. 但是为什么第二种不提示 第四种提示呢?

3. 查看源码

第一种输出 Console.WriteLine(p); 其代码实现如下:

public static void WriteLine(object value)
{
  Console.Out.WriteLine(value);
}
 
public virtual void WriteLine(object value)
{
  if (value == null)
    this.WriteLine();
  else if (value is IFormattable formattable)
    this.WriteLine(formattable.ToString((string) null, this.FormatProvider));
  else
    this.WriteLine(value.ToString());
}

其实最终还是调用了 ToString() 方法.

剩下的输出其实都是调用了 Console.WriteLine(string value) 方法, 如下:

public static void WriteLine(string value)
{
  Console.Out.WriteLine(value);
}
 
public virtual void WriteLine(string value)
{
  if (value == null)
  {
    this.WriteLine();
  }
  else
  {
    int length1 = value.Length;
    int length2 = this.CoreNewLine.Length;
    char[] chArray = new char[length1 + length2];
    value.CopyTo(0, chArray, 0, length1);
    switch (length2)
    {
      case 1:
        chArray[length1] = this.CoreNewLine[0];
        break;
      case 2:
        chArray[length1] = this.CoreNewLine[0];
        chArray[length1 + 1] = this.CoreNewLine[1];
        break;
      default:
        Buffer.InternalBlockCopy((Array) this.CoreNewLine, 0, (Array) chArray, length1 * 2, length2 * 2);
        break;
    }
    this.Write(chArray, 0, length1 + length2);
  }
}

第二种输出格式 是在传参给 WriteLine 方法时, 已经进行了格式化处理, 传入的就是一个 string字符串. 即:

  • 如果不带 ToString 方法, 调用的是 Console.WriteLine(object o) 方法
  • 如果带 ToString() 方法, 调用的是 Console.WriteLine(string value) 方法

所以第二种输出格式不会提示 Redundant 'object.tostring()' call .

而第三四种输出 传入的参数是一个格式化的 string字符串, 内插值字符串.

对于内插值字符串, 官方文档: $ – 字符串内插 – C# 参考 | Microsoft Docs 的一段说明如下:

隐式转换和指定 IFormatProvider 实现的方式

内插字符串有 3 种隐式转换:

  1. 将内插字符串转换为 String 实例,该类例是内插字符串的解析结果,其中内插表达式项被替换为结果的格式设置正确的字符串表示形式。 此转换使用 CurrentCulture 设置表达式结果的格式。
  2. 将内插字符串转换为表示复合格式字符串的 FormattableString 实例,同时也将表达式结果格式化。 这允许通过单个 FormattableString 实例创建多个包含区域性特定内容的结果字符串。 要执行此操作,请调用以下方法之一:

你还可使用 ToString(IFormatProvider) 方法,以提供支持自定义格式设置的 IFormatProvider 接口的用户定义实现。 有关详细信息,请参阅 在 .NET 中设置类型格式 一文中的 使用 ICustomFormatter 进行自定义格式设置 部分。

  1. 将内插字符串转换为 IFormattable 实例,使用此实例也可通过单个 IFormattable 实例创建多个包含区域性特定内容的结果字符串。

意思就是说, 内插值字符串直接传入对象的引用 会实际调用其 ToString() 方法, 所以会提示 Redundant 'object.tostring()' call

4. 总结

从上述分析中看出, 如果我们要覆写父类的方法就必须带上 override 关键字, 否则编译器会认为你写的是一个新的方法,即默认为 new 关键字.