插入字符串的原始类型是什么?
本文关键字:是什么 类型 原始 字符串 插入 | 更新日期: 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
我是一个系统。字符串
我是一个系统。字符串
新的插入字符串语法一部分是编译器的魔法,一部分是运行时类。
让我们看一遍所有的场景,看看到底发生了什么。
-
var s = $"{DateTime.Now}";
编译为:
string s = string.Format("{0}", DateTime.Now);
-
string s = $"{DateTime.Now}";
编译为:
string s = string.Format("{0}", DateTime.Now);
-
object s = $"{DateTime.Now}";
编译为:
object s = string.Format("{0}", DateTime.Now);
-
IFormattable s = $"{DateTime.Now}";
编译为:
IFormattable s = FormattableStringFactory.Create("{0}", new object[] { DateTime.Now });
-
FormattableString s = $"{DateTime.Now}";
编译为:
FormattableString s = FormattableStringFactory.Create("{0}", new object[] { DateTime.Now });
所以我们可以这样总结编译器的魔力:
- 如果我们可以通过使用
string
,通过调用String.Format
创建,然后做 - 如果没有,使用
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
,所以对于重载,我可以观察到规则是,这里我们停止了第一个重载,它有:
-
string
-
FormattableString
-
IFormattable
-
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.IFormattable
或System.FormattableString
使用隐式内插字符串转换(第6.1.4节),内插字符串表达式具有该类型。否则,其类型为string
。
所以隐式内插字符串转换是我所说的隐式转换的正式名称。
他们提到的§6.1.4小节是§6.1 隐式转换的一部分,读作:
隐式内插字符串转换允许内插字符串表达式(§7.6.2)转换为System.IFormattable
或System.FormattableString
(它实现System.IFormattable
)。