MSDN docs包含有关隐式转化的部分:
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);
}
提示:
我是System.String
我是 System.Runtime.CompilerServices.FormattableStringFactory + ConcreteFormattableString
如果您从第二种方法中删除评论,则会获得:
我是一个字符串System.String
我是一个字符串System.String
确定
可能我不理解重载分辨率,但是C# spec的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");
}
删除评论和...
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.String
我是System.Runtime.CompilerServices.FormattableStringFactory + ConcreteFormattableString
我是System.String
我是System.String
答案 0 :(得分:20)
新的插值字符串语法是部分编译器魔术和部分运行时类。
让我们浏览所有场景,看看实际发生了什么。
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存储库,问题和讨论之外,其具体规则尚不清楚(至少对我而言,请证明我错了!)。
因此,上面的例子说明了如果编译器知道目标类型会发生什么,在这种情况下通过变量类型。如果我们调用一个没有重载的单一方法,那就是其中一种类型,完全相同的&#34;魔法&#34;会发生。
但如果我们有超载会怎样?
考虑这个例子:
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
答案 1 :(得分:9)
长话短说:
如果编译器找到带PrintMe
参数的方法string
,则会生成以下代码:
this.PrintMe("Hello World");
this.PrintMe(string.Format("{0}", "Hello World"));
如果您使用PrintMe
参数对方法string
发表评论,则会生成以下代码:
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()
(因为字符串是{{{ 1}})。所以生成的代码是:
object
注意如果删除string.Format("{0}", "Hello World").PrintMe();
的扩展方法,则会出现编译时错误。
答案 2 :(得分:2)
让我们不要让事情变得过于复杂。
字符串插值表达式$"..."
的类型为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
为了完整起见,这里是likely C# Language Specification 6.0,§7.6.2(权威)的措辞:
插值字符串表达式被归类为值。如果是 立即转换为
System.IFormattable
或System.FormattableString
带有隐式插值字符串 转换(第6.1.4节),插值字符串表达式具有该类型。 否则,它的类型为string
。
所以隐式插值字符串转换是我所说的隐式转换的官方名称。
他们提到§6.1.4的小节是§6.1 Implict conversions 的一部分,并且内容为:
隐式插值字符串转换允许插值 要转换为
System.IFormattable
的字符串表达式(第7.6.2节) 或System.FormattableString
(实施System.IFormattable
)。