声明一个不安全的固定自定义结构体数组的解决方法是什么?

8

有没有解决错误CS1663的方法(“固定大小缓冲区类型必须是以下之一:bool、byte、short、int、long、char、sbyte、ushort、uint、ulong、float或double。”)?

我需要从另一个可平移的自定义类型结构声明一个不安全的固定数组,但我卡在了这个编译器错误上。

下面展示一些代码以阐明问题。

struct s1
{
    byte _b1;
    byte _b2;
}

unsafe struct s2
{
    fixed s1 _s1[5]; // CS1663 here...
}

请注意,这两个结构体是可平置的,所以对我来说错误毫无意义。您有任何想法吗?谢谢。

你是指不安全的 struct s2 { fixed s1 s1[5]; } 吗? - Vadim Martynov
是的!!! 我会修复代码片段中的这个小错误,谢谢。@VadimMartynov 你有什么想法吗? - andresantacruz
嗯,你只是有一个语法错误。你应该声明字段修饰符(如private、fixed、readonly、static和其他),字段类型(如byte、int、s1、string)和字段名称(任何用于引用你的变量的名称)。你缺少字段类型(s1)。了解更多关于声明固定缓冲区的信息:https://msdn.microsoft.com/en-us/library/zycewsya(v=vs.80).aspx - Vadim Martynov
@VadimMartynov 我知道我有语法错误 :D 我的问题围绕着“为什么”我会得到这个语法错误。我能想象出这个错误的唯一原因是为了避免在内存中修复一个非blittable类型。关键是我的类型实际上是blittable的。必须有一些解决此限制的方法,这应该是一个非常普遍的问题。 - andresantacruz
5个回答

2

另一个解决方法是使用指针类型字段。

public struct Foo
{
    public byte _b1;
    public byte _b2;
}

public unsafe struct Bar
{
    public int Length;
    public unsafe Foo* Data;
}

如果你的意图是创建一个包含所有自己数据的单一值类型对象(即,它可以在非托管代码中进行memcpy),则唯一的方法是不使用任何数组。

public struct Foo
{
    public byte _b1;
    public byte _b2;
}

public struct Bar1
{
    public Foo F1;
}

public struct Bar2
{
    public Foo F1;
    public Foo F2;
}

public struct Bar3
{
    public Foo F1;
    public Foo F2;
    public Foo F3;
}

2

C# 12(Dotnet 8+)的解决方案

如果您能够使用C# 12(dotnet 8),那么可以使用System.Runtime.CompilerServices.InlineArray属性来创建一个结构体,以表示另一个结构体的固定大小数组,而无需进行任何指针操作。

看一下:

struct MyData
{
    byte _b1;
    byte _b2;
}

// We want our array to be 5 elements long, so specify a size of 5
[System.Runtime.CompilerServices.InlineArray(5)]
struct MyDataInlineArray
{
   // Note that structs with the InlineArray attribute must contain
   // EXACTLY one member.
   MyData Element;
   // In this example, this struct is now a 5-long inline array of MyData. 
}

// Notice that this doesn't have to be marked unsafe anymore.
struct StructWithInlineArray
{
    MyDataInlineArray myDataArray;
}

你可以像普通数组一样使用InlineArray属性来索引类型:
public static void Main() {
    StructWithInlineArray foo = new();

    foo.myDataArray[0] = new MyData();
    foo.myDataArray[1] = ...;

    // You can also just use it directly
    MyDataInlineArray bar = new();
    bar[0] = ...;
}

你可以在这里阅读更多关于它们的内容。

1

这是固定大小缓冲区的限制。

固定数组可以使用任何允许用于常规结构成员的属性或修饰符。唯一的限制是数组类型必须为bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, 或 double之一。

你只能使用这些类型,但不能组合使用(例如struct仅包含这些类型)。无论你的类型是否可位掩码,都没有区别。你只是不能使用它。

因此,你不能将自定义的struct用于固定大小缓冲区。

解决方法?嗯,也许有。你可以改变你的代码结构,并使用类似以下的内容:

unsafe struct s2
{
    fixed byte _b1[5]; 
    fixed byte _b2[5]; 
}

回复我上面的评论: "@VadimMartynov 我知道我有语法错误:D 我的问题围绕着“为什么”我会得到这个语法错误。我能想象出这个错误的唯一原因是为了避免在内存中修复一个非可平坦类型。关键是我的类型实际上是可平坦的。必须有一些解决这个限制的方法,这应该是一个非常普遍的问题。" - andresantacruz
你的解决方法与我的示例不匹配!在你的示例中,我最终会得到一个线性原始缓冲区,其中有5个元素代表_b1和5个元素代表_b2。在我的示例中,内存中的缓冲区应该是相同大小但组织方式不同:首先是_b1的1个元素,然后是_b2的1个元素,以此类推。 - andresantacruz
这就是为什么这个解决方法被称为“解决方法”的原因。 - Vadim Martynov
我知道 :) 我只是说这个解决方法不符合需求。就像我需要10个苹果,但得到了10个香蕉作为解决方法,这样就不合适。如果没有任何适当的解决方法,我将别无选择,只能调整代码以使用Marshal风格 :(((( 这太可悲了... - andresantacruz

0
我如何处理您的示例和类似复杂性的问题,首先要问自己是否可以将结构减少为单个原始类型。如果可以,那么我会通过属性或索引器访问来隐藏结构中的固定缓冲区。
在您的示例中,您有以下内容。
struct s1
{
    byte _b1;
    byte _b2;
}

unsafe struct s2
{
    fixed s1 _s1[5]; // CS1663 here...
}

如果我决定绝对需要结构体在单个连续的数据块中对齐,我可能会考虑像这样的东西。
[StructLayout(LayoutKind.Explicit, Size = 2)]
struct s1
{   // Field offsets to emulate union style access which makes it
    // simple to get at the raw data in a primitive type format.

    [FieldOffset(0)]ushort _u1;
    [FieldOffset(0)]byte _b1;
    [FieldOffset(1)]byte _b2;

    public s1(ushort data)
    {
        _b1 = 0;
        _b2 = 0;
        _u1 = data;
    }

    public ushort ToUShort()
    {
        return _u1;
    }
}

unsafe struct s2
{
    public const int Size = 5;
    private fixed ushort _s1[Size];

    public s1 this[int index]
    {   // A public indexer that provides the data in a friendlier format.
        get
        {
            if (index < 0 || index >= Size )
                throw new IndexOutOfRangeException();
            return new s1(_s1[index]);
        }
        set
        {
            if (index < 0 || index >= Size)
                throw new IndexOutOfRangeException();
            _s1[index] = value.ToUShort();
        }
    }
}

如果这看起来像是一个黑客行为,那是因为它确实如此。我不建议将其作为一般解决方案,因为它很难维护,但在那些罕见的情况下,你在这种低级别上工作,并且事先知道数据规范不会改变,那么类似这样的技术是可行的。但即使在这些情况下,我仍然更喜欢封装尽可能多的低级别内容,以最小化出错的机会。话虽如此,这个方法确实按照要求做到了,即用于解决固定大小缓冲区的自定义结构体的问题。


0
[StructLayout(LayoutKind.Sequential)]
public struct S1
{
    [MarshalAs(UnmanagedType.I1)] public byte _b1; 
    [MarshalAs(UnmanagedType.I1)] public byte _b2; 

    public S1(ushort _ushort)
    {
        this = new Converter(_ushort).s1;
    }
    public ushort USHORT() //for fixed arrays
    {
        return new Converter(this).USHORT;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct Converter
    {
        [FieldOffset(0)] public S1 s1;
        [FieldOffset(0)] public ushort USHORT;

        public Converter(S1 _s1)
        {
            USHORT = 0;
            s1 = _s1;
        }

        public Converter(ushort _USHORT)
        {
            s1 = default;
            USHORT = _USHORT;
        }
    }
}

public unsafe struct S2
{
    public fixed ushort s1[5];

    public S1 this[int n] {
        get => new S1(s1[n]); //copy!
        set => s1[n] = value.USHORT();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接