内插字符串的原始类型是什么?
What is the original type of interpolated string?
MSDN docs 包含有关隐式转换的部分:
var s = $"hello, {name}";
System.IFormattable s = $"Hello, {name}";
System.FormattableString s = $"Hello, {name}";
从第一个字符串开始,内插字符串的原始类型是 string
。好的,我能理解,但是……我意识到字符串没有实现 IFormattable
。所以它看起来像是来自编译器的一些魔法,类似于它对 lambda 的作用。
现在猜一下这段代码的输出:
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);
}
提示:
I am a System.String
I am a
System.Runtime.CompilerServices.FormattableStringFactory+ConcreteFormattableString
如果您从第二种方法中删除注释,您将得到:
I am a string System.String
I am a string System.String
好的
可能是我不太了解重载决议,但是 C# spec 的 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 The call is ambiguous between the following methods or
properties: 'UserQuery.PrintMe(Expression)' and
'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);
}
}
现在我变成这样了:
I am a System.String
I am a System.Runtime.CompilerServices.FormattableStringFactory+ConcreteFormattableString
I am a System.String
I am a System.String
新的内插字符串语法部分是编译器魔法和部分 运行时间 类。
让我们浏览所有场景,看看实际发生了什么。
var s = $"{DateTime.Now}";
编译如下:
string s = string.Format("{0}", DateTime.Now);
详情见Try Roslyn。
string s = $"{DateTime.Now}";
编译如下:
string s = string.Format("{0}", DateTime.Now);
详情见Try Roslyn。
object s = $"{DateTime.Now}";
编译如下:
object s = string.Format("{0}", DateTime.Now);
详情见Try Roslyn。
IFormattable s = $"{DateTime.Now}";
编译如下:
IFormattable s = FormattableStringFactory.Create("{0}", new object[] {
DateTime.Now
});
详情见Try Roslyn。
FormattableString s = $"{DateTime.Now}";
编译如下:
FormattableString s = FormattableStringFactory.Create("{0}", new object[] {
DateTime.Now
});
详情见Try Roslyn。
因此我们可以将编译器的魔力总结如下:
- 如果我们可以只使用
string
,通过调用 String.Format
创建,那么就这样做
- 如果没有,使用
FormattableString
,并通过FormattableStringFactory.Create
创建一个
由于我们还没有正式的 C# 6 标准文档,除了仔细阅读 github 存储库、问题和讨论之外,还不知道确切的规则(至少我不知道,请证明我错了!)。
所以,上面的例子展示了如果编译器知道目标类型会发生什么,在本例中是通过变量类型。如果我们调用一个没有重载且具有其中一种类型的方法,则会发生完全相同的 "magic"。
但是如果我们有重载会怎样?
考虑这个例子:
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
仍然是首选,即使有多个选项。
有关详细信息,请参阅 this .NET fiddle。
注意 .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()
(找到是因为字符串是一个object
)。所以生成的代码是:
string.Format("{0}", "Hello World").PrintMe();
请注意 如果您删除 object
的扩展方法,您将遇到编译时错误。
我们不要把事情搞得太复杂了。
字符串插值表达式$"..."
的类型是string
并且存在从字符串插值表达式$"..."
到类型[=15的隐式转换=].
剩下的只是普通的 C# 重载解析。
如果选择的重载 不需要 隐式转换为 System.FormattableString
,则会创建一个纯字符串(实际上这是用 string.Format
方法)。如果需要隐式转换,则会创建抽象 class System.FormattableString
的一些具体实例(实际上使用 FormattableStringFactory.Create
方法,尽管这是一个实现细节)。
您不需要方法重载来查看这两种基本情况。只要做:
var a = $"..."; // string
FormattableString b = $"..."; // the implicit conversion
与像() => { }
这样的lambda表达式的不同之处在于lambda表达式本身没有类型,它只有有隐式转换。有一个从 lambda 表达式 () => { }
到任何具有正确签名和 return 类型的委托类型 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
为了完整起见,这里是,§7.6.2(权威)的措辞:
An interpolated string expression is classified as a value. If it is
immediately converted to System.IFormattable
or
System.FormattableString
with an implicit interpolated string
conversion (§6.1.4), the interpolated string expression has that type.
Otherwise, it has the type string
.
所以隐式内插字符串转换是我所说的隐式转换的官方名称。
他们提到的 §6.1.4 小节是 §6.1 隐式转换 的一部分,内容如下:
An implicit interpolated string conversion permits an interpolated
string expression (§7.6.2) to be converted to System.IFormattable
or System.FormattableString
(which implements
System.IFormattable
).
MSDN docs 包含有关隐式转换的部分:
var s = $"hello, {name}";
System.IFormattable s = $"Hello, {name}";
System.FormattableString s = $"Hello, {name}";
从第一个字符串开始,内插字符串的原始类型是 string
。好的,我能理解,但是……我意识到字符串没有实现 IFormattable
。所以它看起来像是来自编译器的一些魔法,类似于它对 lambda 的作用。
现在猜一下这段代码的输出:
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);
}
提示:
I am a System.String
I am a System.Runtime.CompilerServices.FormattableStringFactory+ConcreteFormattableString
如果您从第二种方法中删除注释,您将得到:
I am a string System.String
I am a string System.String
好的
可能是我不太了解重载决议,但是 C# spec 的 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 The call is ambiguous between the following methods or properties: 'UserQuery.PrintMe(Expression)' and '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);
}
}
现在我变成这样了:
I am a System.String
I am a System.Runtime.CompilerServices.FormattableStringFactory+ConcreteFormattableString
I am a System.String
I am a System.String
新的内插字符串语法部分是编译器魔法和部分 运行时间 类。
让我们浏览所有场景,看看实际发生了什么。
var s = $"{DateTime.Now}";
编译如下:
string s = string.Format("{0}", DateTime.Now);
详情见Try Roslyn。
string s = $"{DateTime.Now}";
编译如下:
string s = string.Format("{0}", DateTime.Now);
详情见Try Roslyn。
object s = $"{DateTime.Now}";
编译如下:
object s = string.Format("{0}", DateTime.Now);
详情见Try Roslyn。
IFormattable s = $"{DateTime.Now}";
编译如下:
IFormattable s = FormattableStringFactory.Create("{0}", new object[] { DateTime.Now });
详情见Try Roslyn。
FormattableString s = $"{DateTime.Now}";
编译如下:
FormattableString s = FormattableStringFactory.Create("{0}", new object[] { DateTime.Now });
详情见Try Roslyn。
因此我们可以将编译器的魔力总结如下:
- 如果我们可以只使用
string
,通过调用String.Format
创建,那么就这样做 - 如果没有,使用
FormattableString
,并通过FormattableStringFactory.Create
创建一个
由于我们还没有正式的 C# 6 标准文档,除了仔细阅读 github 存储库、问题和讨论之外,还不知道确切的规则(至少我不知道,请证明我错了!)。
所以,上面的例子展示了如果编译器知道目标类型会发生什么,在本例中是通过变量类型。如果我们调用一个没有重载且具有其中一种类型的方法,则会发生完全相同的 "magic"。
但是如果我们有重载会怎样?
考虑这个例子:
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
仍然是首选,即使有多个选项。
有关详细信息,请参阅 this .NET fiddle。
注意 .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()
(找到是因为字符串是一个object
)。所以生成的代码是:
string.Format("{0}", "Hello World").PrintMe();
请注意 如果您删除 object
的扩展方法,您将遇到编译时错误。
我们不要把事情搞得太复杂了。
字符串插值表达式$"..."
的类型是string
并且存在从字符串插值表达式$"..."
到类型[=15的隐式转换=].
剩下的只是普通的 C# 重载解析。
如果选择的重载 不需要 隐式转换为 System.FormattableString
,则会创建一个纯字符串(实际上这是用 string.Format
方法)。如果需要隐式转换,则会创建抽象 class System.FormattableString
的一些具体实例(实际上使用 FormattableStringFactory.Create
方法,尽管这是一个实现细节)。
您不需要方法重载来查看这两种基本情况。只要做:
var a = $"..."; // string
FormattableString b = $"..."; // the implicit conversion
与像() => { }
这样的lambda表达式的不同之处在于lambda表达式本身没有类型,它只有有隐式转换。有一个从 lambda 表达式 () => { }
到任何具有正确签名和 return 类型的委托类型 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
为了完整起见,这里是
An interpolated string expression is classified as a value. If it is immediately converted to
System.IFormattable
orSystem.FormattableString
with an implicit interpolated string conversion (§6.1.4), the interpolated string expression has that type. Otherwise, it has the typestring
.
所以隐式内插字符串转换是我所说的隐式转换的官方名称。
他们提到的 §6.1.4 小节是 §6.1 隐式转换 的一部分,内容如下:
An implicit interpolated string conversion permits an interpolated string expression (§7.6.2) to be converted to
System.IFormattable
orSystem.FormattableString
(which implementsSystem.IFormattable
).