【问题标题】:Struct within Struct, able to change inner Struct typeStruct 中的 Struct,可以更改内部 Struct 类型
【发布时间】:2010-12-20 05:30:33
【问题描述】:

对此没有太多解释,这就是我所拥有的:

public struct PACKET_HEADER
    {
        public string computerIp;
        public string computerName;
        public string computerCustomName;
    };

    public struct PACKET
    {
        public PACKET_HEADER pktHdr;
        public PACKET_DATA pktData;
    };


    public struct PACKET_DATA
    {
        public Command command;
        public string data;  
    };

    public struct DATA_MESSAGE
    {
        public string message;
    };

    public struct DATA_FILE
    {
        public string fileName;
        public long fileSize;       
    };

基本上我希望 PACKET_DATA 中的数据字段可以是 DATA_FILE 或 DATA_MESSAGE。我知道需要更改类型,但我不知道要更改什么,泛型是一种选择吗?

最终结果应该是我可以做到的:

pktData.data.fileName 或者 pktData.data.message

编辑

我能做到:

public struct PACKET_DATA
{
    public Command command;
    public string data;
    public DATA_MESSAGE data_message;
    public DATA_FILE data_file;
};

当我不需要它们时,只需将 data_message 或文件设置为 null?这将如何影响序列化/字节数组和正在发送的数据。如果我使用类,我不会有同样的问题

编辑 2

public struct PACKET_MESSAGE
{
    public PACKET_HEADER pktHdr;
    public Command command;
    public DATA_MESSAGE pktData;
};

public struct PACKET_FILE
{
    public PACKET_HEADER pktHdr;
    public Command command;
    public DATA_FILE pktData;
};

编辑 3

我有一个消毒器和去消毒器,可以与我的原始示例一起使用,如果没有问题,那么实际的序列化就完成了。

编辑 4

一切似乎都在工作,除了我的序列化程序正在“尝试读取或写入受保护的内存。这通常表明其他内存已损坏”。 gunna 在发布我的工作解决方案时看看它:)

编辑 5

    public static byte[] Serialize(object anything)
    {
        int rawsize = Marshal.SizeOf(anything);
        byte[] rawdatas = new byte[rawsize];
        GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
        IntPtr buffer = handle.AddrOfPinnedObject();
        Marshal.StructureToPtr(anything, buffer, false);
        handle.Free();
        return rawdatas;
    }

    public static object Deserialize(byte[] rawdatas, Type anytype)
    {
        int rawsize = Marshal.SizeOf(anytype);
        if (rawsize > rawdatas.Length)
            return null;
        GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
        IntPtr buffer = handle.AddrOfPinnedObject();
        object retobj = Marshal.PtrToStructure(buffer, anytype);
        handle.Free();
        return retobj;
    } 

最终

结构:

public struct PACKET_HEADER
{
    public string computerIp;
    public string computerName;
    public string computerCustomName;
};

public struct PACKET
{
    public PACKET_HEADER pktHdr;
    public PACKET_DATA pktData;
};

public struct PACKET_DATA
{
    public Command command;
    public IDATA data;
    public T GetData<T>() where T : IDATA
    {
        return (T)(data);
    }
}

public interface IDATA { }

public struct DATA_MESSAGE : IDATA
{
    public string message;
}

public struct DATA_FILE : IDATA
{
    public string fileName;
    public long fileSize;
}

如何创建一个新的数据包(可能可以合并在一起):

    public static PACKET CreatePacket(Command command)
    {
        PACKET packet;
        packet.pktHdr.computerIp = Settings.ComputerIP;
        packet.pktHdr.computerName = Settings.ComputerName;
        packet.pktHdr.computerCustomName = Settings.ComputerCustomName;

        packet.pktData.command = command;
        packet.pktData.data = null;

        return packet;
    }

    public static PACKET CreatePacket(Command command, DATA_MESSAGE data_message)
    {
        PACKET packet;
        packet.pktHdr.computerIp = Settings.ComputerIP;
        packet.pktHdr.computerName = Settings.ComputerName;
        packet.pktHdr.computerCustomName = Settings.ComputerCustomName;

        packet.pktData.command = command;
        packet.pktData.data = data_message;

        return packet;
    }

    public static PACKET CreatePacket(Command command, DATA_FILE data_file)
    {
        PACKET packet;
        packet.pktHdr.computerIp = Settings.ComputerIP;
        packet.pktHdr.computerName = Settings.ComputerName;
        packet.pktHdr.computerCustomName = Settings.ComputerCustomName;

        packet.pktData.command = command;
        packet.pktData.data = data_file;

        return packet;
    }

(de) 上面的序列化。

简单示例:

PACKET packet = Packet.CreatePacket(command, data_file);
byte[] byData = Packet.Serialize(packet);

另一端:

PACKET returnPacket = (PACKET)Packet.Deserialize(socketData.dataBuffer, typeof(PACKET));
                        // Get file
string fileName = returnPacket.pktData.GetData<DATA_FILE>().fileName;
long fileSize = returnPacket.pktData.GetData<DATA_FILE>().fileSize;

一切似乎都很好,花花公子:)

【问题讨论】:

  • 只是对您的编辑的澄清:作为值类型的结构永远不会为空。您需要一个标志来指示哪个成员是有效的。
  • 是的,我刚刚意识到它们不能为空。我想有两种类型的数据包,见上文,这更有意义吗?
  • 好的,但请记住反序列化的代码需要确定类型。我真的很喜欢你的两个子结构版本,你只需要一个bool isMessagePacket;
  • 另外关于您的内存访问错误,您使用什么方法将字节数组转换为结构,反之亦然?由于您的结构已经包含字符串,这是一个类引用,因此没有内联存储在结构中,我不确定您为什么要使用结构。您也可以只使用类,用 [Serializable] 标记整个事物并使用 BinaryFormatter 进行序列化。
  • @Paul 看看 Edit 5,嗯,课程似乎开始变得更好了

标签: c# struct


【解决方案1】:

这个问题需要一个明确的答案,所以我试着总结一下:

如果你想获取一个 C# 数据结构并将其转换为字节数组,你可以使用结构和编组,或使用类(或结构,但你为什么要这样做)和序列化框架(如 BinaryFormatter ),或自定义序列化逻辑(如BinaryWriter)。我们可以争论哪个更好,但现在让我们假设我们正在使用结构,并且我们正在使用编组。虽然我会说,结构体非常有限,应该主要用于必要时与 Win32 API 函数进行互操作。

所以问题是,我们有一个容器结构,它可能包含两种类型的子结构之一。如果您要编组一个结构,那么诸如泛型和/或为您的子结构类型使用通用接口之类的东西将不会成功。基本上,您唯一的选择是让容器同时具有结构和一个布尔标志,指示要使用哪个结构。这有增加数据包大小的缺点,因为您也在发送未使用的子结构。

在手头的情况下,结果如下所示:

public struct PACKET_DATA
{
    public Command command;
    public string data;
    public bool is_message_packet;
    public DATA_MESSAGE data_message;
    public DATA_FILE data_file;
};

也就是说,在您的情况下,使用结构和编组只会在您自己的进程中真正起作用,因为您的结构包含字符串。当结构包含指向非固定长度字符串的指针时,这些字符串将分配到其他地方,并且不会成为您复制的字节数组的一部分,只有指向它们的指针才是。您还需要在某个时候调用 Marshal.DestroyStructure,并使用您传递给 StructureToPtr 的 IntPtr 来清理这些字符串资源。

所以这个故事的寓意:你能制作出你最初要求的结构吗:是的。您是否应该像现在这样使用它们:不。因为您尝试通过网络发送可变大小的数据结构(我假设该结构称为 PACKET),所以结构不起作用,您确实需要使用某种序列化框架或自定义序列化逻辑。

【讨论】:

  • 看看我最后的编辑 Paul,一切似乎都正常
  • 我认为它“工作”的事实是一种幻想,因为您在单个进程中使用测试代码。获取该代码,并尝试通过网络套接字发送数据,我几乎可以肯定它会失败。这是因为你的结构包含的字符串没有与结构一起存储,封送的结构只有指向它们的指针,它们被分配到其他地方。
  • 另外,当一个结构包含一个接口成员时,它把那个成员当作一个引用。换句话说,父结构包含一个指针,而不是包含子结构内联。这绝对不会飞。
【解决方案2】:
    public struct PACKET_DATA
    {
        public IData data;
        public T GetData<T>() where T : IDATA
        {
           return (T)data;
        }
    }

    public interface IDATA { }

    public struct DATA_MESSAGE : IDATA
    {
        public string message;
    }

    public struct DATA_FILE : IDATA
    {
        public string fileName;
        public long fileSize;
    }

PACKET_DATA packetData = new PACKET_DATA();
packetData.data = new DATA_MESSAGE();
var message = packetData.GetData<DATA_MESSAGE>().message;

【讨论】:

  • 公共结构 PACKET { 公共 PACKET_HEADER pktHdr;公共 PACKET_DATA pktData; };这不是以前的问题吗? 如果它的 T 之间有什么,那么 PACKET 结构也必须采用 T ?弄得一团糟
  • 我试过实现这个,除了“return T(data);”中的 T 之外,它看起来都很好它返回错误 'T' is a 'type parameter' 但用作'变量'
  • @Metalstorm:这是一个错字。应该是(T)data
【解决方案3】:

您可以使用泛型来做到这一点,但随后类型参数将传播到 PACKET 结构,我猜这会使其难以使用并且不是您想要的。

在这里使用结构而不是类的目的是什么?是为了互操作吗? (在这种情况下,互操作场景将决定正确的解决方案)。还是为了避免装箱/堆分配?

【讨论】:

  • “你可以用泛型来做到这一点,但是类型参数会传播到 PACKET 结构中,我猜这会让使用起来很尴尬,而不是你想要的。”是的,我看过了,但它确实变得非常混乱。 Packet 结构(及其内容)被序列化并作为字节数组通过网络发送。目的只是在短时间内存储少量信息,我读到结构比类更好。
  • 主要是想要高效的序列化吗?如果是这样,最佳方法是编写自定义序列化程序。使这些类型类而不是结构(就 GC 而言)的开销并不大。
【解决方案4】:

定义一个两个结构都继承自的虚假接口怎么样?当然这不会解决您的序列化问题,但正如前面所说,您可能仍然需要自定义序列化方法。

public interface IDataType
{
}

public struct PACKET_DATA
{
    public Command command;
    public IDataType data;  
};

public struct DATA_MESSAGE : IDataType
{
    public string message;
};

public struct DATA_FILE : IDataType
{
    public string fileName;
    public long fileSize;       
};

【讨论】:

  • 这看起来不错,但是当我尝试 PACKET 数据包时;数据包.pktData.data。除了标准的 ToString 等
猜你喜欢
  • 1970-01-01
  • 2015-01-29
  • 1970-01-01
  • 2014-10-28
  • 1970-01-01
  • 1970-01-01
  • 2011-11-14
  • 2021-01-17
  • 1970-01-01
相关资源
最近更新 更多