【问题标题】:Do C# generics prevent autoboxing of structs in this case?在这种情况下,C# 泛型是否会阻止结构的自动装箱?
【发布时间】:2015-07-16 14:52:46
【问题描述】:

通常,将结构 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 方法在I 类型的对象上调用foo(),所以一旦我们用S 调用它,S 就会被装箱。 doFooGeneric 方法做同样的事情。然而,一旦我们用S 调用它,就不需要自动装箱,因为运行时知道如何在S 上调用foo()。但这会完成吗?或者运行时会盲目地将S 绑定到I 以调用接口方法?

【问题讨论】:

  • 尝试添加struct约束 - 即void doFooGeneric&lt;T&gt;(T t) where T : struct, I {
  • FWIW,使用来自this answerIsBoxed 函数表示它没有装箱。我不知道为什么的细节。
  • 相关(如果不重复):stackoverflow.com/questions/3032750/…,特别是 Marc Gravell 的 this answer
  • @JamesThorpe 据我所知,您不能使用该方法查看t 是否在t.foo() 调用中被装箱。
  • @JeppeStigNielsen 你不会……你会在doFooGeneric里面做吗? Like this?

标签: c# generics interface autoboxing


【解决方案1】:
void doFooGeneric<T>(T t) where T : I {
    t.foo(); // <--- Will an S be boxed here??
}

那里可以避免拳击!

结构类型S 是密封的。对于上面的方法doFooGeneric 的类型参数T 的值类型版本,C# 编译器会提供直接调用相关结构成员的代码,无需装箱。

这很酷。

有关一些技术细节,请参阅 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&lt;&gt; 的引用,并且还保存“计数器”,它记住我们到目前为止已经达到的List&lt;&gt; 中的索引。

我必须使用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);
}

【讨论】:

  • 如果是这样,那么是的,那将非常酷。但是你确定吗? Habib 的回答恰恰相反。
  • @gexicide,不,我对where T: I 的回答是错误的,否则它是正确的。
  • 这种优化对于 Array.Sort 来说非常重要。这应该有效。
【解决方案2】:

因为Constrained Opcodes在第二种情况下发挥作用,将避免拳击。

【讨论】:

    猜你喜欢
    • 2012-07-10
    • 1970-01-01
    • 2016-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-22
    • 1970-01-01
    • 2021-12-31
    相关资源
    最近更新 更多