Подтвердить что ты не робот

Каков исходный тип интерполированной строки?

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

Ok
Может быть, я не очень хорошо понимаю перегрузку разрешения, но 14.4.2 из С# spec подразумевает, что тип переданного параметра определяется первым, но снова как делаются работы лямбда?

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(выражение)' и 'UserQuery.PrintMe(Действие)

Поэтому я не понимаю поведение компилятора.

Update:

Чтобы усугубить ситуацию, я проверил это поведение для методов расширения:

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

4b9b3361

Ответ 1

Короче говоря:

Если компилятор находит метод 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() (он найден, поскольку строка является object). Таким образом, сгенерированный код:

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

Примечание, что если вы удалите метод расширения для object, вы получите ошибку времени компиляции.

Ответ 2

Новый интерполированный синтаксис строки - это магия компилятора части и части времени выполнения.

Пропустите все сценарии и посмотрите, что на самом деле происходит.

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

    Это компилируется следующим образом:

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

    Подробнее см. Try Roslyn.

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

    Это компилируется следующим образом:

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

    Подробнее см. Попробуйте Roslyn.

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

    Это компилируется следующим образом:

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

    Подробнее см. Попробуйте Roslyn.

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

    Это компилируется следующим образом:

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

    Подробнее см. Попробуйте Roslyn.

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

    Это компилируется следующим образом:

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

    Подробнее см. Попробуйте Roslyn.

Итак, мы можем суммировать магию компилятора следующим образом:

  • Если мы сможем использовать только string, созданный с вызовом String.Format, тогда сделайте это
  • Если нет, используйте FormattableString и создайте его через FormattableStringFactory.Create

Поскольку у нас еще нет документа официальных документов С# 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

Ответ 3

Не будем усложнять ситуацию.

Тип выражения интерполяции строк $"..." - string, и существует неявное преобразование из выражения интерполяции строк $"..." в тип System.FormattableString.

Остальное - просто обычное разрешение перегрузки С#.

Если выбрана перегрузка, для которой не требуется неявное преобразование в System.FormattableString, создается простая строка (на практике это реализуется с помощью метода string.Format). Если требуется неявное преобразование, создается конкретный экземпляр абстрактного класса System.FormattableString (на практике с помощью метода FormattableStringFactory.Create, хотя это деталь реализации).

Вам не нужны перегрузки методов, чтобы увидеть эти два основных случая. Просто выполните:

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

Разница с лямбда-выражением типа () => { } заключается в том, что выражение лямбда не имеет типа в себе, оно имеет только неявные преобразования. Существует одно неявное преобразование из выражения 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

Для полноты, вот формулировка вероятной спецификации языка С# 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).