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

Pav*_*nin 29 c# string overloading type-inference string-interpolation

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

var s = $"hello, {name}";
System.IFormattable s = $"Hello, {name}";
System.FormattableString s = $"Hello, {name}";
Run Code Online (Sandbox Code Playgroud)

从第一个字符串开始,原始类型的插值字符串就是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);
}
Run Code Online (Sandbox Code Playgroud)

暗示:

我是System.String
我是System.Runtime.CompilerServices.FormattableStringFactory + ConcreteFormattableString

如果您从第二种方法中删除评论,您将获得:

我是一个字符串System.String
我是一个字符串System.String

好的
我可能不太了解重载分辨率,但是C#规范的 14.4.2 意味着首先定义传递参数的类型,但是lambdas如何工作呢?

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");
}
Run Code Online (Sandbox Code Playgroud)

删除评论和...

CS0121以下方法或属性之间的调用不明确:'UserQuery.PrintMe(Expression)'和'UserQuery.PrintMe(Action)'

所以我不明白编译器的行为.

更新:

为了使事情变得更糟,我已经检查了扩展方法的这种行为:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我就是这样的:

我是System.String
我是System.Runtime.CompilerServices.FormattableStringFactory + ConcreteFormattableString
我是System.String
我是一个System.String

ang*_*son 22

新的插值字符串语法是部分编译器魔术和部分运行时类.

让我们浏览所有场景,看看实际发生了什么.

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

    这被编译为:

    string s = string.Format("{0}", DateTime.Now);
    
    Run Code Online (Sandbox Code Playgroud)

    有关详细信息,请参阅Try Roslyn.

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

    这被编译为:

    string s = string.Format("{0}", DateTime.Now);
    
    Run Code Online (Sandbox Code Playgroud)

    有关详细信息,请参阅Try Roslyn.

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

    这被编译为:

    object s = string.Format("{0}", DateTime.Now);
    
    Run Code Online (Sandbox Code Playgroud)

    有关详细信息,请参阅Try Roslyn.

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

    这被编译为:

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

    有关详细信息,请参阅Try Roslyn.

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

    这被编译为:

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

    有关详细信息,请参阅Try Roslyn.

所以我们可以总结一下编译器的魔力如下:

  1. 如果我们可以通过使用string,通过调用创建String.Format,然后这样做
  2. 如果没有,请使用FormattableString,并创建一个viaFormattableStringFactory.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"); }
}
Run Code Online (Sandbox Code Playgroud)

执行此示例时,我们得到此输出:

string
Run Code Online (Sandbox Code Playgroud)

string即使有多种选择,显然仍然是首选.

有关详细信息,请参阅此.NET小提琴.

请注意,.NET Fiddle由于某种原因不允许我FormattableString直接使用,但如果我运行相同的代码,并且存在该重载,在LINQPad中,我仍然得到string输出.

如果我然后删除了string我得到的重载FormattableString,然后如果我删除了我得到的IFormattable,那么使用重载我可以观察规则是,并且在这里我们停止第一个重载:

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


Zei*_*kki 9

长话短说:

如果编译器找到PrintMestring参数的方法,则会生成以下代码:

this.PrintMe("Hello World");
this.PrintMe(string.Format("{0}", "Hello World"));
Run Code Online (Sandbox Code Playgroud)

如果PrintMe使用string参数注释方法,则会生成以下代码:

this.PrintMe("Hello World");
this.PrintMe(FormattableStringFactory.Create("{0}", new object[] {"Hello World"}));
Run Code Online (Sandbox Code Playgroud)

然后,方法过载决定的部分我很容易猜测.

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

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

这是基于编译器的决定:

var s1 = $"{ "Hello World"}";
Run Code Online (Sandbox Code Playgroud)

生成(作为最佳选择):

string s1 = string.Format("{0}", "Hello World");
Run Code Online (Sandbox Code Playgroud)

和:

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

PrintMe($"{ "Hello World"}");
Run Code Online (Sandbox Code Playgroud)

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

this.PrintMe(FormattableStringFactory.Create("{0}", new object[] {"Hello World"}));
Run Code Online (Sandbox Code Playgroud)

对于扩展方法:

$"{"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);
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器$"{"Hello World"}"首先解析,这导致string作为最佳决策,然后检查是否PrintMe()找到了一个方法(由于字符串是一个,因此找到它object).所以生成的代码是:

string.Format("{0}", "Hello World").PrintMe();
Run Code Online (Sandbox Code Playgroud)

请注意,如果删除扩展方法object,则会出现编译时错误.

  • 我个人觉得这种行为是一种很好的方式来拍摄自己的脚. (2认同)