在这种情况下,C#泛型是否会阻止结构的自动装箱? [英] Do C# generics prevent autoboxing of structs in this case?

查看:171
本文介绍了在这种情况下,C#泛型是否会阻止结构的自动装箱?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通常,将一个struct S 视为接口 I 会触发该结构的自动装箱,这可能会产生影响如果经常这样做,就会提高性能但是,如果我编写一个采用类型参数 T:I 的泛型方法并使用 S 调用它,那么将编译器省略了拳击,因为它知道类型 S 并且不必使用接口?



代码显示了我的观点:

 接口I {
void foo();
}

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

class Y {

void doFoo(I i){
i.foo();
}
void doFooGeneric< T>(T t)其中T:I {
t.foo(); //< --- S会在这里装盒吗?
}

public static void Main(string [] args){
S x;
doFoo(x); // x被装箱
doFooGeneric(x); // x没有装盒,至少不在这里,对吧?


$ b $ / code>

doFoo 方法调用 foo()在类型为 I 的对象上,所以一旦我们调用它有一个 S S 会被装箱。 doFooGeneric 方法做同样的事情。但是,一旦我们用 S 调用它,就不需要自动装箱,因为运行时知道如何调用 foo() S 上。但是这会完成吗?或者将运行时盲目地将 S 改为 I 来调用接口方法?


(T t)其中T:I {
t.foo();}其中,T表示一个表达式。 //< --- S会在这里装盒吗?
}

在那里避免拳击!

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



这是很酷的。



关于某些技术细节,请参阅Sameer的答案。 / p>




好的,我想出了一个这样的例子。我会对更好的例子感兴趣,如果任何人有一些:

  using System; 
使用System.Collections.Generic;

命名空间AvoidBoxing
{
静态类程序
{
static void Main()
{
var myStruct = new List< ; INT> {10,20,30,} .GetEnumerator();
myStruct.MoveNext(); //在列表中移动到'10'

//
//仅限处理*一个*这些电话:
//

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

Console.WriteLine(在调用之后,current现在是:+ myStruct.Current); // 10或20?
}

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

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

这里 myStruct 是一个可变值类型,它保存一个引用回到 List<>< / code&记得在 List<>< / code>中我们已经达到了什么样的索引。



我不得不使用 ref ,否则当传递给任何一个方法时值类型将被值复制!



当我取消注释时到 UseMyStruct (仅),这个方法把我们的值类型中的counter移动到前面的一个位置。如果它是在值类型的盒装副本中完成的,我们就不会在原始的结构实例中看到它。



要查看与拳击有什么不同,尝试调用 UseMyStructAndBox 代替(再次评论 UseMyStruct )。它会在演员表中创建一个框,并且 MoveNext 发生在副本上。所以输出是不同的!






对于那些对不满意(或困惑) ref ,只需从方法内写出 Current 即可。然后我们可以摆脱 ref 。例如:

  static void F (T t)其中T:IEnumerator  
{
t.MoveNext(); //确定,没有装箱
Console.WriteLine(t.Current);
}

static void G< T>(T t)其中T:IEnumerator< int>
{
((IEnumerator< int>)t).MoveNext(); //我们说Box!,它会包装盒; '移动'碰巧复制
Console.WriteLine(t.Current);
}


Usually, treating a struct S as an interface I will trigger autoboxing of the struct, which can have impacts on performance if done often. However, if I write a generic method taking a type parameter T : I and call it with an S, then will the compiler omit the boxing, since it knows the type S and does not have to use the interface?

This code shows my point:

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?
    }

}

The doFoo method calls foo() on an object of type I, so once we call it with an S, that S will get boxed. The doFooGeneric method does the same thing. However, once we call it with an S, no autoboxing might be required, since the runtime knows how to call foo() on an S. But will this be done? Or will the runtime blindly box S to an I to call the interface method?

解决方案

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

Boxing will be avoided there!

The struct type S is sealed. For value type versions of the type parameter T to your method doFooGeneric above, the C# compiler gives code that calls the relevant struct member directly, without boxing.

Which is cool.

See Sameer's answer for some technical details.


OK, so I came up with an example of this. I will be interested in better examples if anyone has some:

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();
    }
  }
}

Here the type of myStruct is a mutable value-type which holds a reference back to the List<>, and also holds "counter" that remembers what index in the List<> we have reached until now.

I had to use ref, otherwise the value-type would be copied by value when passed into either of the methods!

When I uncomment the call to UseMyStruct (only), this method moves the "counter" inside our value type one position ahead. If it did that in a boxed copy of the value type, we would not see it in the original instance of the struct.

To see what the difference is with boxing, try the call UseMyStructAndBox instead (comment UseMyStruct again). It creates a box at the cast, and the MoveNext happens on a copy. So the output is different!


To those who are unhappy with (or confused by) the ref, just write out Current from within the method instead. Then we can get rid of ref. Example:

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);
}

这篇关于在这种情况下,C#泛型是否会阻止结构的自动装箱?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆