【问题标题】:How can I cast Memory<T> to another如何将 Memory<T> 投射到另一个
【发布时间】:2019-02-04 06:52:09
【问题描述】:

我们可以使用MemoryMarshal.Cast 方法重载将Span&lt;T&gt;ReadOnlySpan&lt;T&gt; 转换为另一个。喜欢:

Span<byte> span = stackalloc byte[4];
var singleIntSpan = MemoryMarshal.Cast<byte, int>(span);

但是有没有办法将Memory&lt;T&gt; 转换为另一个?例如将Memory&lt;byte&gt; 转换为Memory&lt;ushort&gt;

【问题讨论】:

    标签: c#


    【解决方案1】:

    你不能直接;然而,如果你真的需要,你可以创建一个自定义的MemoryManager&lt;T&gt;(可能实际上是一个MyMemoryManager&lt;TFrom, TTo&gt; : MemoryManager&lt;TTo&gt;,它作为GetSpan()覆盖的一部分执行转换。这有点不平凡,并且需要另一个分配 - 不像 Span&lt;T&gt; 演员,它是免费的。

    如果你需要一个具体的例子,我可以做一个(我实际上在一些现有的代码中就是这样做的),但是:老实说,你可能想重新考虑这个场景。

    编辑:像这样:

    using System;
    using System.Buffers;
    using System.Runtime.InteropServices;
    
    class Program
    {
        static void Main()
        {
            Memory<byte> bytes = new byte[1024];
    
            Memory<ushort> typed = Utils.Cast<byte, ushort>(bytes);
            Console.WriteLine(typed.Length); // 512
    
            // note CPU endianness matters re the layout
            typed.Span[0] = 0x5432;
            Console.WriteLine(bytes.Span[0]); // 50 = 0x32
            Console.WriteLine(bytes.Span[1]); // 84 = 0x54
        }
    }
    
    static class Utils
    {
        public static Memory<TTo> Cast<TFrom, TTo>(Memory<TFrom> from)
            where TFrom : unmanaged
            where TTo : unmanaged
        {
            // avoid the extra allocation/indirection, at the cost of a gen-0 box
            if (typeof(TFrom) == typeof(TTo)) return (Memory<TTo>)(object)from;
    
            return new CastMemoryManager<TFrom, TTo>(from).Memory;
        }
        private sealed class CastMemoryManager<TFrom, TTo> : MemoryManager<TTo>
            where TFrom : unmanaged
            where TTo : unmanaged
        {
            private readonly Memory<TFrom> _from;
    
            public CastMemoryManager(Memory<TFrom> from) => _from = from;
    
            public override Span<TTo> GetSpan()
                => MemoryMarshal.Cast<TFrom, TTo>(_from.Span);
    
            protected override void Dispose(bool disposing) { }
            public override MemoryHandle Pin(int elementIndex = 0)
                => throw new NotSupportedException();
            public override void Unpin()
                => throw new NotSupportedException();
        }
    }
    

    如果你真的想支持 pin/unpin,那应该是可能的 - 你只需要计算来自竞争的 TFrom/TTo 的相对范围和偏移量 - 大概使用 Unsafe.SizeOf&lt;T&gt; 等,并使用MemoryMarshal.TryGetMemoryManager 获取底层内存管理器(如果有的话 - 请注意裸数组没有内存管理器)。除非您要广泛测试该选项,否则投掷可能比弄错更安全。

    【讨论】:

    • 哇,这太棒了,我一直在绞尽脑汁想办法解决这个问题,在这个黑魔法中牺牲了很多处女!
    • @usr 抱歉,我的意思是 gen-0 - 即一个可以立即被 GC 收集的对象(盒子)(当下一次 gen-0 扫描发生时),永远不会存活到下一代
    • @MichaelRandall 不,不是;基本上,Memory&lt;T&gt; 只是一个偏移量+计数和一个object 参考。 object 可以T[],但也可以是MemoryManager&lt;T&gt;。如果是后者,那么实际跨度为((MemoryManager&lt;T&gt;)obj).GetSpan().Slice(offset, count);如果它是一个数组,那么它是new Span&lt;T&gt;((T[])obj, offset, count)。仅此而已。任何所有权等都是完全独立的。
    • @moien 好问题,但我的看法是:一个完整​​的 pin/unpin 实现需要比我愿意为这个示例编写的代码多得多,带有互锁计数器,跟踪 MemoryHandle ,以及所有用于比较 TFrom/TTo 大小的数学运算。所以...“读者练习”
    • @MarcGravell 我处于 Zach 的境地。为什么投射Span&lt;T&gt;Memory&lt;T&gt; 容易得多?类型几乎相同。
    【解决方案2】:

    我不认为你可以,但是我想你可以从中返回一个跨度,尽管我怀疑它会有所帮助

    Memory.Span Property

    从当前实例返回一个跨度。

    var array = new int[4];
    var mem = array.AsMemory();
    var span = MemoryMarshal.Cast<int, byte>(mem.Span);
    

    【讨论】:

    • 感谢您的回答,但我已经提到了 'Span' 铸造。
    • 有一些方法......但它们是邪恶的 - 看我的回答
    猜你喜欢
    • 2019-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 2018-11-28
    • 2017-02-09
    相关资源
    最近更新 更多