Бокс по строкам при вызове ToString()

Я часто задавался вопросом, действительно ли происходит следующий сценарий в С#

Если у меня есть структура, но я явно не переопределяю ни один из методов, которые производятся от объекта, такого как ToString(), GetHashCode() и т.д., тогда, если я объявляю локальный экземпляр моего класса структуры и вызываю "ToString ( ) 'на нем, будет ли моя структура получаться в коробке, т.е. CLR конвертирует ее неявно в объект в куче, а затем вызывает ToString()? Или это достаточно умно, чтобы знать, что для этой структуры нет реализации и игнорировать ее?

то есть

public struct Vector2D
{
    public float m_x;
    public float m_y;


    ...... etc
}


void SomeFunc()
{
  Vector2D aVec = new Vector2D();
  Console.WriteLine(aVec.ToString()); // <-- does aVec get boxed here?
  ..... 
}

== Редактировать - Обновить == Mehrdad ссылка на MSDN, в то время как полезность сбила меня с толку. Я процитирую и посмотрю, может ли кто-нибудь раскрыть это для меня.

Когда команда метода callvirt имеет префикс ограниченного thisType, инструкция выполнена следующим образом:

Если этот тип является ссылочным типом (как против типа значения), то ptr разыменован и передан как 'this'указатель на callvirt метода.

Если этот тип является типом значения и thisType реализует метод, тогда ptr прошел без изменений в качестве 'this'указатель на инструкцию метода вызова, для реализации метода путем thisType.

Если этот тип является типом значения и thisType не реализует метод то ptr разыменовывается, помещается в коробку и передается как указатель 'this' на callvirt.

Значит ли это, что если я не буду явно использовать ToString() для моего типа структуры, то он попадет в последний случай и получит коробку? Или я не понимаю его где-то?

Ответ 1

Если thisType - тип значения и thisType не реализует метод то ptr разыменовывается, помещается в коробку и передается как указатель 'this' на callvirt.

Этот последний случай может произойти только тогда, когда метод был определен на Object, ValueType или Enum, а не переопределено на thisType. В этом случае бокс вызывает копию исходного объекта которые будут сделаны.

Ответ да, тип значения в коробке. Вот почему всегда полезно переопределять ToString() в пользовательских структурах.

Ответ 2

Изменить: ответ kek444. Приношу свои извинения за неправильное понимание вопроса. Я оставляю свой ответ здесь, так как считаю, что он имеет дополнительную ценность и релевантную информацию для будущих читателей.

Я также считаю, что эта цитата из ссылка в Mehrdad отвечает особенно заставляя думать:

  • Если thisType - тип значения и thisType не реализует метод то ptr разыменовывается, помещается в коробку и передается как указатель 'this' на callvirt.

Этот последний случай может произойти только тогда, когда метод был определен для объекта, ValueType или Enum и не переопределяется по этому типу. В этом случае бокс вызывает копию исходного объекта быть произведенным. Однако, поскольку ни одна из методы Object, ValueType и Enum изменяет состояние объекта, Этот факт не может быть обнаружен.

Таким образом, нельзя писать программу, чтобы продемонстрировать, что бокс происходит. Это видно только при взгляде на ИЛ и в полном понимании префикса constrained для инструкции callvirt.


Из раздела 11.3.5 спецификации языка С# в http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc (http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx):

Когда тип структуры переопределяет виртуальный метод, унаследованный от System.Object(например, Equals, GetHashCode или ToString), вызов виртуального метода через экземпляр типа struct не вызывает возникновения бокса. Это справедливо даже тогда, когда структура используется как параметр типа, а вызов происходит через экземпляр типа параметра типа. Например:

using System;
struct Counter
{
    int value;
    public override string ToString() {
        value++;
        return value.ToString();
    }
}
class Program
{
    static void Test<T>() where T: new() {
        T x = new T();
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
    }
    static void Main() {
        Test<Counter>();
    }
}

Вывод программы:

1
2
3

Несмотря на то, что для ToString плохой стиль имеет побочные эффекты, пример демонстрирует отсутствие бокса для трех вызовов x.ToString().

Точно так же бокс никогда не подразумевается при доступе к члену по параметру ограниченного типа. Например, предположим, что интерфейс ICounter содержит метод Increment, который можно использовать для изменения значения. Если ICounter используется как ограничение, реализация метода Increment вызывается с ссылкой на переменную, на которую был вызван Increment, и никогда не была в штучной упаковке.

using System;
interface ICounter
{
    void Increment();
}
struct Counter: ICounter
{
    int value;
    public override string ToString() {
        return value.ToString();
    }
    void ICounter.Increment() {
        value++;
    }
}
class Program
{
    static void Test<T>() where T: ICounter, new() {
        T x = new T();
        Console.WriteLine(x);
        x.Increment();                      // Modify x
        Console.WriteLine(x);
        ((ICounter)x).Increment();      // Modify boxed copy of x
        Console.WriteLine(x);
    }
    static void Main() {
        Test<Counter>();
    }
}

Первый вызов Increment изменяет значение переменной x. Это не эквивалентно второму вызову Increment, который изменяет значение в коробочной копии x. Таким образом, выход программы:

0
1
1

Подробнее о боксе и распаковке см. в разделе 4.3.

Ответ 3

Нет, он не помещается в бокс при вызове ToString или GetHashCode, если он реализован вашей структурой (зачем это нужно? constrained инструкция IL забота об этом.) Он помещается в бокс, когда вы вызываете не виртуальный метод (или виртуальный метод, который не переопределяется в структуре) на System.Object (его базовый класс), то есть GetType/MemberwiseClone.

ОБНОВЛЕНИЕ: Извините за недоразумение, которое оно могло вызвать. Я написал ответ с переопределением методов в структуре в виду (почему я упоминал, что не виртуальные методы нуждаются в боксе, я должен был бы более откровенно не путать читателей, тем более, что я пропустил ваше утверждение относительно того, чтобы не отменять метод), как будто вы не переопределяете его, метод Object.ToString ожидает его первый аргумент (ссылка на this) как ссылочный тип (экземпляр Object). Очевидно, что значение должно быть помещено в этот вызов (как вызов в базовом классе.)

Однако, суть в том, что характер вызова виртуального метода по типу значения не приводит к при выдаче команды box (в отличие от не виртуальных методов на Object, которые всегда приводят к в испускании явной команды box.) Это инструкция callvirt, которая будет делать бокс, если она должна прибегнуть к реализации Object.ToString (как вы упомянули в обновленном вопросе) так же, как когда вы передаете структуру в метод, который ожидает параметр Object.