C# - 是否可以装箱?

时间:2011-09-12 15:04:43

标签: c# memory-management boxing

Boxing将值类型转换为对象类型。或者正如MSDN所说,装箱是一种“将结构包装在托管堆上的引用类型对象内的操作。”

但是如果你试着通过查看IL代码来深入研究,那么你只能看到神奇的单词“box”。

推测,我猜运行时有一些基于泛型的秘密类,比如Box<T>具有public T Value属性,而装箱int看起来像:

int i = 5;
Box<int> box = new Box<int>;
box.Value = 5;

取消整理int会便宜得多:return box.Value;

不幸的是,我的性能饥渴的服务器应用程序确实有点拳击,特别是小数。更糟糕的是,这些盒子是短暂的,这使我怀疑我支付了两次,一次用于装箱,然后再用垃圾收集箱子后我就完成了。

如果我自己解释这个记忆,我会考虑在这里使用一个对象池。但是由于实际的对象创建隐藏在IL中的魔术词后面,我有什么选择?

我的具体问题:

  • 是否存在一种诱导运行时从池中取出盒子而不是实例化它们的现有机制?
  • 拳击期间创建的实例的类型是什么?是否可以手动控制装箱过程,但仍然可以与拆箱兼容?

如果最后一个问题看起来很奇怪,我的意思是我可以创建自己的Box<T>DecimalBox类,汇集它,并手动打开/取消框。但是我不想去修改使用盒装值的代码中的各个位置(也就是unbox它)。

5 个答案:

答案 0 :(得分:10)

  

猜测,我猜运行时有一些基于泛型的秘密类

你的推测几乎是正确的。 逻辑上你可以认为一个盒子是一个神奇的Box<T>类型,其行为与你描述的一样(带有一些神奇的东西;例如,可以为空的值类型框的方式是一个有点不寻常。)作为一个实际的实现细节,运行时不会使用泛型类型。拳击存在于CLR v1中,这是在将泛型类型添加到类型系统之前。

  

我的性能饥渴的服务器应用程序做了一些拳击,特别是小数。

如果你这样做会伤害,那么停止这样做。而不是试图使拳击更便宜,而不是首先停止做。你为什么拳击小数?

  

更糟糕的是,这些盒子是短暂的,这让我怀疑我支付了两次,一次用于装箱,然后再用垃圾收集箱子后我就完成了。

昙花一现更好而不是长寿;使用短暂的堆对象,你需要付费才能收集它们一次然后它们就死了。 对于长期存在的堆对象,当对象继续存活时,您会反复支付该成本。

当然,您可能担心短期物品的成本不是收集本身的成本。相反,它是集合压力;分配的更多短期对象等于更频繁的垃圾收集。

分配成本非常低。在GC堆上移动指针,将小数复制到该位置,完成。

  

如果我自己解释这个记忆,我会考虑在这里使用一个对象池。

右;您需要支付更多收集长寿命物品的费用,但由于产生的收集压力较小,因此总收入较少。这可能是一场胜利。

  

是否存在一种诱导运行时从池中取出盒子而不是实例化它们的现有机制?

不。

  

拳击期间创建的实例的类型是什么?是否可以手动控制装箱过程,但仍然可以与拆箱兼容?

框的类型是被装箱的东西的类型。只需通过调用GetType来询问它;它会告诉你。盒子很神奇;它们是它们所包含的东西的类型。

就像我之前说的那样,而不是试图让拳击更便宜,只是不要在第一时间做。

答案 1 :(得分:4)

运行时几乎完全按照您的描述进行操作,但不涉及泛型,因为泛型不是原始框架的一部分。

如果你使用的是一些需要盒装价值的代码,你可以做很多关于拳击的事情。您可以创建一个使用相同语法的对象,通过覆盖隐式转换来返回值,但这仍然需要是一个对象,并且您基本上可以完成相同数量的工作。

尝试汇集盒装值很可能会降低性能而不是增加性能。垃圾收集器专门用于有效处理短期对象,如果将对象放入池中,它们将成为长寿命对象。当对象在垃圾收集中存活时,它们将被移动到下一个堆,这涉及将对象从内存中的一个位置复制到另一个位置。因此,通过汇集对象,实际上可能会为垃圾收集器带来更多的工作,而不是更少。

答案 2 :(得分:2)

默认情况下,没有类似Box<T>的类。类型仍然是原始类型,但是是参考。由于Decimal不可变,因此您无法在创建后更改该值。所以你不能真正使用普通盒装小数的池。

您可以通过实现缓存来避免装箱重新出现的值。或者您需要实现自己的盒子类型。

虽然您无法使用标准演员从object取消装箱。因此,您需要调整消费代码。


编写自己的方法,返回一个盒装值:

[ThreadStatic]
private static Dictionary<T,object> cache;

public object BoxIt<T>(T value)
  where T:struct
{
  if(cache==null)
    cache=new Dictionary<T,object>();
  object result;
  if(!cache.TryGetValue(T,result))
  {
    result=(object)T;
    cache[value]=result;
  }
  return result;
}

这个简单实现的一个问题是缓存永远不会删除项目。即这是一个内存泄漏。

答案 3 :(得分:0)

int i = 5;
object boxedInt = i;

将值类型分配给System.Object或多或少都与拳击有关,就你的代码而言(我不会进入拳击操作技术细节)。

System.Object个变量中保留小数值可以减少拳击和创建System.Object个实例的时间,但是你总是需要取消装箱。如果你不得不经常更改这些值,这就变得更难以实现,因为每次更改都是一项任务,因此至少是拳击。

虽然这种做法有一个例子 - .Net框架在类似于此类的类中内部使用预先设置的布尔值:

class BoolBox
{
    // boxing here
    private static object _true = true;
    // boxing here
    private static object _false = false;

    public static object True { get { return _true; } }
    public static object False { get { return _false; } }
}

WPF系统经常使用System.Object个变量用于依赖属性,只是为了说明即使在这些“现代”中装箱/拆箱也是不可避免的情况。

答案 4 :(得分:0)

不幸的是你无法挂钩拳击过程,但是,你可以使用隐式转换来使它“看起来像拳击”。

我还避免将每个值存储在Dictionary中 - 你的记忆问题会变得更糟。这是一个有用的拳击框架。

public class Box
{
    internal Box()
    { }

    public static Box<T> ItUp<T>(T value)
        where T : struct
    {
        return value;
    }

    public static T ItOut<T>(object value)
        where T : struct
    {
        var tbox = value as Box<T>;
        if (!object.ReferenceEquals(tbox, null))
            return tbox.Value;
        else
            return (T)value;
    }
}

public sealed class Box<T> : Box
    where T : struct
{
    public static IEqualityComparer<T> EqualityComparer { get; set; }
    private static readonly ConcurrentStack<Box<T>> _cache = new ConcurrentStack<Box<T>>();
    public T Value
    {
        get;
        private set;
    }

    static Box()
    {
        EqualityComparer = EqualityComparer<T>.Default;
    }

    private Box()
    {

    }

    ~Box()
    {
        if (_cache.Count < 4096) // Note this will be approximate.
        {
            GC.ReRegisterForFinalize(this);
            _cache.Push(this);
        }
    }

    public static implicit operator Box<T>(T value)
    {
        Box<T> box;
        if (!_cache.TryPop(out box))
            box = new Box<T>();
        box.Value = value;
        return box;
    }

    public static implicit operator T(Box<T> value)
    {
        return ((Box<T>)value).Value;
    }

    public override bool Equals(object obj)
    {
        var box = obj as Box<T>;
        if (!object.ReferenceEquals(box, null))
            return EqualityComparer.Equals(box.Value, Value);
        else if (obj is T)
            return EqualityComparer.Equals((T)obj, Value);
        else
            return false;
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

// Sample usage:
var boxed = Box.ItUp(100);
LegacyCode(boxingIsFun);
void LegacyCode(object boxingIsFun)
{
  var value = Box.ItOut<int>(boxingIsFun);
}

老实说,你应该提出另一个问题 - 并就如何摆脱这个拳击问题寻求建议。