插入字符串的原始类型是什么?

本文关键字:是什么 类型 原始 字符串 插入 | 更新日期: 2023-09-27 17:53:33

MSDN文档包含有关隐式转换的部分:

var s = $"hello, {name}";
System.IFormattable s = $"Hello, {name}";
System.FormattableString s = $"Hello, {name}";

从第一个字符串可以得出插入字符串的原始类型是string。好吧,我能理解它,但后来……我意识到字符串不实现IFormattable。所以它看起来像是编译器的魔法,类似于它对lambdas的作用。

现在猜测这段代码的输出:

void Main()
{
    PrintMe("Hello World");
    PrintMe($"{ "Hello World"}");
}
void PrintMe(object message)
{
    Console.WriteLine("I am a " + message.GetType().FullName);
}
//void PrintMe(string message)
//{
//  Console.WriteLine("I am a string " + message.GetType().FullName);
//}
void PrintMe(IFormattable message)
{
    Console.WriteLine("I am a " + message.GetType().FullName);
}

提示:

我是系统。String
我是aSystem.Runtime.CompilerServices.FormattableStringFactory + ConcreteFormattableString

如果你从第二个方法中删除注释,你将得到:

我是一个字符串系统。字符串
我是一个字符串系统。字符串


可能是我不太理解重载解析,但是c#规范的14.4.2意味着首先定义传递的参数的类型,那么lambda是如何工作的呢?

void Main()
{
    PrintMe(() => {});
    PrintMe(() => {});
}
void PrintMe(object doIt)
{
    Console.WriteLine("I am an object");
}
//void PrintMe(Expression<Action> doIt)
//{
//  Console.WriteLine("I am an Expression");
//}
void PrintMe(Action doIt)
{
    Console.WriteLine("I am a Delegate");
}

删除注释和…

CS0121调用在以下方法之间是二义性的属性:'UserQuery.PrintMe(Expression)'和"UserQuery.PrintMe(行动)的

所以我不明白这里编译器的行为。

更新:

更糟糕的是,我检查了扩展方法的这种行为:

void Main()
{
    PrintMe("Hello World");
    PrintMe($"{"Hello World"}");
    "Hello World".PrintMe();
    $"{"Hello World"}".PrintMe();
}
void PrintMe(object message)
{
    Console.WriteLine("I am a " + message.GetType().FullName);
}
void PrintMe(IFormattable message)
{
    Console.WriteLine("I am a " + message.GetType().FullName);
}
public static class Extensions
{
    public static void PrintMe(this object message)
    {
        Console.WriteLine("I am a " + message.GetType().FullName);
    }
    public static void PrintMe(this IFormattable message)
    {
        Console.WriteLine("I am a " + message.GetType().FullName);
    }
}

现在是这样的:

我是系统。字符串
我是System.Runtime.CompilerServices.FormattableStringFactory+ConcreteFormattableString
我是一个系统。字符串
我是一个系统。字符串

插入字符串的原始类型是什么?

新的插入字符串语法一部分是编译器的魔法,一部分是运行时类。

让我们看一遍所有的场景,看看到底发生了什么。

  1. var s = $"{DateTime.Now}";

    编译为:

    string s = string.Format("{0}", DateTime.Now);
    

  2. string s = $"{DateTime.Now}";

    编译为:

    string s = string.Format("{0}", DateTime.Now);
    

  3. object s = $"{DateTime.Now}";

    编译为:

    object s = string.Format("{0}", DateTime.Now);
    

  4. IFormattable s = $"{DateTime.Now}";

    编译为:

    IFormattable s = FormattableStringFactory.Create("{0}", new object[] {
        DateTime.Now
    });
    

  5. FormattableString s = $"{DateTime.Now}";

    编译为:

    FormattableString s = FormattableStringFactory.Create("{0}", new object[] {
        DateTime.Now
    });
    

所以我们可以这样总结编译器的魔力:

  1. 如果我们可以通过使用string,通过调用String.Format创建,然后做
  2. 如果没有,使用FormattableString,并通过FormattableStringFactory.Create创建一个

由于我们还没有官方的c# 6标准文档,除了仔细阅读github存储库、问题和讨论之外,确切的规则是未知的(至少对我来说不是,请证明我错了!)。

所以,上面的例子展示了如果编译器知道目标类型,在这个例子中是通过变量类型。如果我们调用一个单一的方法,没有重载,具有这些类型之一,将会发生完全相同的"魔法"。

但是如果我们有重载会发生什么呢?

考虑这个例子:

using System;
public class Program
{
    public static void Main()
    {
        Test($"{DateTime.Now}");
    }
    public static void Test(object o) { Console.WriteLine("object"); }
    public static void Test(string o) { Console.WriteLine("string"); }
    public static void Test(IFormattable o) { Console.WriteLine("IFormattable"); }
    // public static void Test(FormattableString o) { Console.WriteLine("FormattableString"); }
}

当执行这个例子时,我们得到这样的输出:

string

所以很明显string仍然是首选,即使有多个选项可用。

详细信息请参阅此。net提琴

请注意,. net Fiddle出于某种原因不允许我直接使用FormattableString,但如果我运行相同的代码,过载存在,在LINQPad中,我仍然得到string作为输出。

如果我删除string重载,我得到FormattableString,然后如果我删除它,我得到IFormattable,所以对于重载,我可以观察到规则是,这里我们停止了第一个重载,它有:

  1. string
  2. FormattableString
  3. IFormattable
  4. object

长话短说:

如果编译器发现一个带有string参数的方法PrintMe,它生成以下代码:

this.PrintMe("Hello World");
this.PrintMe(string.Format("{0}", "Hello World"));

如果用string参数注释PrintMe方法,它将生成以下代码:

this.PrintMe("Hello World");
this.PrintMe(FormattableStringFactory.Create("{0}", new object[] {"Hello World"}));

那么,方法重载的决定部分我猜是相当容易的。

this.PrintMe("Hello World");选择object参数方法,因为"Hello World"不能隐式转换为IFormattable

那么,插入字符串的原始类型是什么?

这是基于编译器的决定:

var s1 = $"{ "Hello World"}";

生成(作为最佳选项):

string s1 = string.Format("{0}", "Hello World");

:

void PrintMe(IFormattable message)
{
    Console.WriteLine("I am a " + message.GetType().FullName);
}
PrintMe($"{ "Hello World"}");

生成(为了匹配方法签名):

this.PrintMe(FormattableStringFactory.Create("{0}", new object[] {"Hello World"}));

对于扩展方法:

$"{"Hello World"}".PrintMe();
public static class Extensions
{
    public static void PrintMe(this object message)
    {
        Console.WriteLine("I am a " + message.GetType().FullName);
    }
    public static void PrintMe(this IFormattable message)
    {
            Console.WriteLine("I am a " + message.GetType().FullName);
    }
}

编译器首先解析$"{"Hello World"}",这导致string作为最佳决策,然后检查是否有PrintMe()方法找到(它被发现,因为string是object)。因此生成的代码是:

string.Format("{0}", "Hello World").PrintMe();

注意如果您删除object的扩展方法,您将获得编译时错误。

我们不要把事情弄得太复杂了。

字符串插值表达式$"..."的类型为string,并且存在从字符串插值表达式$"..."System.FormattableString类型的隐式转换。

其余的只是普通的c#重载解析。

如果选择的重载不需要将隐式转换为System.FormattableString,则创建一个普通字符串(实际上这是通过string.Format方法实现的)。如果需要隐式转换,则创建抽象类System.FormattableString的一些具体实例(在实践中使用FormattableStringFactory.Create方法,尽管这是实现细节)。

您不需要方法重载来查看这两种基本情况。只做:

var a = $"...";               // string
FormattableString b = $"..."; // the implicit conversion 

() => { }等lambda表达式的区别在于lambda表达式本身没有类型,只有具有隐式转换。有一个从lambda表达式() => { }到具有正确签名和返回类型的任何委托类型D的隐式转换,加上一个到System.Linq.Expressions.Expression<D>类型的隐式转换,其中D是该委托类型。

var p = () => {};                                // BAD, compile-time error
Action q = () => {};                             // OK, one implicit conversion
SomeAppropriateDelType r = () => {};             // OK, another implicit conversion
Expression<Action> s  = () => {};                // OK, another implicit conversion
Expression<SomeAppropriateDelType> t = () => {}; // OK, another implicit conversion

为了完整起见,下面是可能来自c#语言规范6.0,§7.6.2(权威)的措辞:

插入的字符串表达式归类为值。如果是的话立即转换为System.IFormattableSystem.FormattableString使用隐式内插字符串转换(第6.1.4节),内插字符串表达式具有该类型。否则,其类型为string

所以隐式内插字符串转换是我所说的隐式转换的正式名称。

他们提到的§6.1.4小节是§6.1 隐式转换的一部分,读作:

隐式内插字符串转换允许内插字符串表达式(§7.6.2)转换为System.IFormattableSystem.FormattableString(它实现System.IFormattable)。