Предоставляют ли генерирующие средства С# автообъекты структур в этом случае?

Обычно обработка struct S в качестве интерфейса I приведет к автобоксированию структуры, которая может иметь влияние на производительность, если это делается часто. Однако, если я напишу общий метод, берущий параметр типа T : I и вызывая его с помощью S, тогда компилятор опустит бокс, так как он знает тип S и не должен использовать интерфейс?

Этот код показывает мою точку зрения:

interface I{
    void foo();
}

struct S : I {
    public void foo() { /* do something */ }
}

class Y {

    void doFoo(I i){
        i.foo();
    }
    void doFooGeneric<T>(T t) where T : I {
        t.foo(); // <--- Will an S be boxed here??
    }

    public static void Main(string[] args){
        S x;
        doFoo(x); // x is boxed
        doFooGeneric(x); // x is not boxed, at least not here, right?
    }

}

Метод doFoo вызывает foo() объекта типа I, поэтому, как только мы назовем его S, этот S получит коробку. Метод doFooGeneric делает то же самое. Однако, как только мы вызываем его с помощью S, автооблок не требуется, поскольку среда выполнения знает, как вызвать foo() на S. Но это будет сделано? Или будет ли окно S вслепую в I вызвать метод интерфейса?

Ответ 1

void doFooGeneric<T>(T t) where T : I {
    t.foo(); // <--- Will an S be boxed here??
}

Бокса там можно избежать!

Тип структуры S запечатан. Для версий типа значений параметра типа T для вашего метода doFooGeneric выше, компилятор С# дает код, который вызывает соответствующий член структуры напрямую, без бокса.

Что круто.

См. ответ Sameer для некоторых технических деталей.


Хорошо, поэтому я придумал пример этого. Меня будут интересовать лучшие примеры, если у кого-то есть:

using System;
using System.Collections.Generic;

namespace AvoidBoxing
{
  static class Program
  {
    static void Main()
    {
      var myStruct = new List<int> { 10, 20, 30, }.GetEnumerator();
      myStruct.MoveNext(); // moves to '10' in list

      //
      // UNCOMMENT ONLY *ONE* OF THESE CALLS:
      //

      //UseMyStruct(ref myStruct);
      //UseMyStructAndBox(ref myStruct);

      Console.WriteLine("After call, current is now: " + myStruct.Current); // 10 or 20?
    }

    static void UseMyStruct<T>(ref T myStruct) where T : IEnumerator<int>
    {
      myStruct.MoveNext();
    }

    static void UseMyStructAndBox<T>(ref T myStruct)
    {
      ((IEnumerator<int>)myStruct).MoveNext();
    }
  }
}

Здесь тип myStruct является изменяемым типом значений, который возвращает ссылку на List<>, а также содержит "счетчик", который запоминает, какой индекс в List<> мы достигли до сих пор.

Мне пришлось использовать ref, иначе тип значения будет скопирован по значению при передаче в любой из методов!

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

Чтобы узнать, какая разница в боксе, попробуйте вместо этого вызов UseMyStructAndBox (комментарий UseMyStruct снова). Он создает поле в роли, а MoveNext происходит на копии. Таким образом, выход отличается!


Для тех, кто недоволен (или запутался) ref, просто напишите Current изнутри метода. Тогда мы можем избавиться от ref. Пример:

static void F<T>(T t) where T : IEnumerator<int>
{
  t.MoveNext(); // OK, not boxed
  Console.WriteLine(t.Current);
}

static void G<T>(T t) where T : IEnumerator<int>
{
  ((IEnumerator<int>)t).MoveNext(); // We said "Box!", it will box; 'Move' happens to a copy
  Console.WriteLine(t.Current);
}