Строковый конструктор

Можно сказать,

string myString = "Hello";

Какая "магически" создает новый строковый объект, содержащий это значение.

Почему нельзя использовать подобный подход "без построения" для объектов, созданных из классов, которые мы определяем в нашем коде? Какая "магия", которую делает VS для string s? И для enum s?

Я никогда не видел объяснений, как это работает.

Ответы

Ответ 1

В основном, это часть спецификации языка С#: там синтаксис для строковых литералов, числовых литералов, символьных литералов и булевых литералов, но все.

Компилятор использует эти литералы для генерации IL, и для большинства из них существует подходящая инструкция для "константы определенного типа", поэтому она непосредственно представлена. Единственным исключением является decimal, который не является примитивным типом в терминах CLR и поэтому должен иметь дополнительную поддержку. (Вот почему вы не можете указать аргумент decimal при применении атрибута, например.)

Самый простой способ увидеть, что происходит, - использовать ildasm (или аналогичный инструмент), чтобы посмотреть на IL, сгенерированный для любого конкретного бита исходного кода.

С точки зрения создания ваших собственных классов вы можете обеспечить неявное преобразование из string (или что-то еще) в ваш собственный тип, но это не будет иметь такого же эффекта. Вы можете написать исходный код:

MyType x = "hello";

... но это не будет "константой" типа MyType... это будет просто инициализатор, который использовал ваше неявное преобразование.

Ответ 2

Фактически вы можете сделать это для своих пользовательских классов. Это достигается путем определения ваших собственных неявных преобразований из других типов. Это очень хорошо описано в msdn: http://msdn.microsoft.com/en-us/library/aa288476%28v=vs.71%29.aspx

Здесь приведен пример для строки:

class Email
{
    private string user;
    private string domain;

    public Email(string user, string domain)
    {
        this.user = user;
        this.domain = domain;
    }

    static public implicit operator Email(string value) // magic goes here ;)
    {
        var parts = value.Split('@');
        if (parts.Length != 2)
            return null;

        return new Email(parts[0], parts[1]);
    }

    static public implicit operator string(Email value)
    {
        return "{ User = " + value.user + ", Domain = " + value.domain + " }";
    }
}

class Test
{
    static public void Main()
    {
        Email test = "[email protected]"

        System.Console.WriteLine("Test: " + test);
    }
}

Ответ 3

Компилятор С# превращает это в соответствующую инструкцию CIL: ldstr. Для вашего собственного сложного типа нет эквивалента, поэтому компилятор должен выпустить инструкцию newobj CIL, которая вызывает конструктор вашего типа. Синтаксис, который вы предлагаете, будет скрывать этот вызов конструктора от пользователя.

Ответ 4

Хотя фактическая механика немного отличается от того, что я опишу здесь, важно понять, что строка не создается, когда выполняется код string myString = "Hello";. Скорее, строка создается при загрузке кода.

Код для каждой сборки содержит большой блок данных двоичных данных, который считывается в массив вместе с кодом. Если код содержит 23 разных строковых литерала, то содержимое всех этих литералов появится в массиве вместе с 23 записями, каждый из которых перечисляет начальный индекс и длину одной из строк. Этот процесс концептуально похож на:

char[] RawData;  // Gets loaded by the runtime
string [] StringLiterals;

void create_strings()
{
  int numStrings = (int)RawData[0] + 65536*(int)RawData[1];
  StringLiterals= new string[numStrings];
  for (int i=0; i<numStrings; i++)
  {
    int header = i*4+2;
    int startLoc = (int)RawData[header] + 65536*(int)RawData[header+1];
    int length  = (int)RawData[header+2] + 65536*(int)RawData[header+3];
    StringsLiterals[i] = new String(RawData, startOfs, length);
  }
}

Если "Hello" является 7-й строкой, определенной в сборке, тогда символы "Hello" появятся в RawData в позиции, определенной в записи №7. Вышеупомянутый оператор затем будет переведен как string myString = StringLiterals[7]; - не создает новый объект, а просто возвращает ссылку на объект, который был создан при загрузке класса.