.NET中可否存储与类型无关的数据?

天堂里的死神 程序员  2010-10-16 05:51:58
比较小白的问题,小生C++学出来的,C#不是太熟悉,序列化这里有些疑惑。
:P
正在做一个序列化相关的模块,可能会面临几种状况:

1 主要用于序列化struct和value类型,但包括struct数组和value数组、List、KeyValuePair等的存档需求。
C++中,因为这些类型的内存布局是可知的,无论是不是数组,存盘只要取一个首地址ptr,一个长度length,就可以用下面的方式:save(ptr, length)。C#的Stream虽然可以写到byte[]里,但是怎么从struct、Value类型以及它们的数组、list、KeyValuePair中取到这个byte[]呢?
最好是用引用的方式获取(就是将一个struct等等强制转换为一个byte[])。


2 可能会序列化第三方的.NET Assembly中的类(主要会是struct),这些类可能没有[Serializable] Attribute。
因此不能使用binary Formatter,而且binary formatter一直没认真看下去Reflector,感觉里面绕的太多,不知道会不会有性能损失。
这种情况下,如果能实现1的方法,则我就不用再去做什么TypeDescriptor了,引入那个概念可能会让程序变得很难懂,而且让用户可以接管的东西太多,怕后面导致难以维护。


3 可能会处理下面的情况:在Assembly 1 中,一个Namespace1.Class1被存入文件,而用户后来会升级到Assembly2,这时候同一个Class,内存布局没有任何变更的情况下,Namespace可能变了,变成了Namespace2.Class1,这时候如何去处理。
我在存这个Class1的时候,肯定是存了它的名字的,但是我发现用名字的话,无论哪种方法都是无法保证读的时候能定位到正确的Class1的:
如果存全名,那这里肯定读不出来了,上面说的那个NS1转到NS2就没戏。
如果仅存Assembly名+Class名,那么Assembly里如果有同名Class也没戏(同名不同namespace)。
不知道怎样能我怎么存进去的Class,读出来的时候也能够唯一正确地定位到这个Class——无论Assembly怎么样换。(这里面有个假设:Class的本名是绝对不变的)

谢谢大家。
...全文
82 点赞 收藏 12
写回复
12 条回复
天堂里的死神 2011年02月16日
感谢大家的回复,去年底一直忙到现在,上网的时间都没有。
现在的做法是跟主程学的,使用Emit分析类并用IL来生成存档和读档的方法,还没完全弄完,加班加到吐,希望后面有点时间,弄完了再向大家请教、学习 ^_^
再次感谢。
回复 点赞
yaazz 2010年10月17日
[Quote=引用 10 楼 fangxinggood 的回复:]
学艺不精,没看出来用怎么load。。。
[/Quote]
我再贴个吧:)

/// <summary>
/// BYTE数组转结构体
/// </summary>
/// <param name="bytes">byte数组</param>
/// <param name="type">结构类型</param>
/// <returns>转换后的结构体</returns>
public static T Byte2Struct<T>(byte[] bytes, Type type, int startIndex) where T : struct
{
//得到结构体的大小
int size = Marshal.SizeOf(type);
//byte数组长度小于结构体的大小
if (size > bytes.Length - startIndex)
{
return default(T);
}
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将byte数组COPY到分配好的内存空间
Marshal.Copy(bytes, startIndex, structPtr, size);
//将内存空间转换为目标结构体
T obj = (T)Marshal.PtrToStructure(structPtr, type);
//free内存空间
Marshal.FreeHGlobal(structPtr);
//返回结构体
return obj;
}

回复 点赞
机器人 2010年10月16日
学艺不精,没看出来用怎么load。。。
回复 点赞
天堂里的死神 2010年10月16日
晕,对,我给说反了。
主要是担心那个中间会多一个内存的分配和拷贝,不知道会有多大的开销。
Reflector那个方法刚刚没想到,因为之前比较笨没有用Marshal所以只能用名字来做判断……晕倒中。您的方法很好,不过毕竟还是要逐个Property去分析。我在想因为内存布局因为一般是没有动的,如果要是能直接写内存进去就好了。
准备把这两种方法都试一下,Marshal这个和unsafe ptr都试试看。:)
回复 点赞
机器人 2010年10月16日
另外,关于属性拷贝,如果属性名相同就可以用反射来做的。


public static void CopyProperty(object fromObj, object toObj)
{
Type fromType = fromObj.GetType();
Type toType = toObj.GetType();

PropertyInfo[] fromPis = fromType.GetProperties();
PropertyInfo[] toPis = toType.GetProperties();

foreach (PropertyInfo pi in fromPis)
{
PropertyInfo toProperty = toType.GetProperty(pi.Name);
if (toProperty != null)
{
toProperty.SetValue(toObj, pi.GetValue(fromObj, null), null);
}
}
}
回复 点赞
机器人 2010年10月16日
我看 yaazz 的代码应该是save的过程呀。

save: struct -> byte[] -> local file
load: local file -> byte[] -> struct

lz不是想要这样的过程吗?
回复 点赞
天堂里的死神 2010年10月16日
突然想到,不知道用unsafe的是否可以?
比如:
int size = Marshal.SizeOf(The_Struct);
byte* _ptr = (byte*)&The_Struct;
for (int i = 0 ; i < size ; ++i)
{
The_Stream.Write(*_ptr);
++_ptr;
}
不过即便这样也只能处理Save过程。
Load过程看来还是只能用Marshal做一次内存拷贝。
回复 点赞
天堂里的死神 2010年10月16日
to wuyq11:
多谢。
但主要因为有第二点的存在,所以可能用不了binaryformatter,而且大概看了一下Reflector,感觉里面挺绕的,对它的性能比较担心。
回复 点赞
天堂里的死神 2010年10月16日
to fangxingood:
多谢。不用数据库是因为想做的尽可能通用。:)
我所面临的主要是跟磁盘文件打交道,很多第三方软件导出的资源什么的都是磁盘文件。
属性拷贝是我现在用的方式,因为要记名字,名字很长,后面的数据,可能却只有一个byte。因此性能非常低,存出的文件也很大,跟我用C++做的同一个目的的东西差别太大了。

to yaazz:
多谢。Marshal之前没用过,我好好看看。
想问一下,我看Marshal的意思是要先分配一个跟Struct等大小的数据区,然后把Struct拷贝到这个数据区中?然后再把这个数据区拷贝为一个Byte[]数组?这中间会有两次拷贝过程。
另外,这是Load过程吧?如果使用Marshal来做Save的话,就需要相反的PtrToStructure过程,这个过程也是一个拷贝过程了。
因为做的可能是对性能会有一定要求的场合,因此:这3个拷贝过程是否能有机会跳过去呢?

回复 点赞
wuyq11 2010年10月16日
可以把指定的一些类型转换为字节数组
public static byte[] ObjectToBytes(object obj)
{
using (MemoryStream ms = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
return ms.GetBuffer();
}
}

struct
[Serializable]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct test
{
public Byte a;
public int b;
public int GetSize()
{
return sizeof(byte) + sizeof(int);
}
public byte[] StructToBytes()
{
byte[] bytes = new byte(GetSize());
int index = 0;
BitConverter.GetBytes(a).CopyTo(bytes, index);
index += sizeof(byte);
BitConverter.GetBytes(b).CopyTo(bytes, index);
return bytes;
}
}


回复 点赞
yaazz 2010年10月16日
 /// <summary>
/// 结构体转BYTE数组
/// </summary>
/// <param name="structObj">要转换的结构体</param>
/// <returns>转换后的BYTE数组</returns>
public static byte[] Struct2Bytes<T>(T structObj) where T : struct
{
//得到结构体大小
int size = Marshal.SizeOf(structObj);
//创建byte数组
byte[] bytes = new byte[size];
//分配结构体大小的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(size);
//将结构体COPY到分配好的内存空间
Marshal.StructureToPtr(structObj, structPtr, false);
//从内存空间COPY到BYTE数组
Marshal.Copy(structPtr, bytes, 0, size);
//free 内存空间
Marshal.FreeHGlobal(structPtr);
//返回byte数组
return bytes;
}
回复 点赞
机器人 2010年10月16日
1. 应该只能通过Binary Formatter来序列化。

// Create a hashtable of values that will eventually be serialized.
Hashtable addresses = new Hashtable();
addresses.Add("Jeff", "123 Main Street, Redmond, WA 98052");
addresses.Add("Fred", "987 Pine Road, Phila., PA 19116");
addresses.Add("Mary", "PO Box 112233, Palo Alto, CA 94301");

// To serialize the hashtable and its key/value pairs,
// you must first open a stream for writing.
// In this case, use a file stream.
FileStream fs = new FileStream("DataFile.dat", FileMode.Create);


2. 按照LZ的要求的话,只能放弃BinaryFormatter这条道了,自己定义XML格式持久化吧。

3. 命名空间会改变的话,反射都没有办法了,只能是保留纯数据,再用属性拷贝的方式,
复制到新类里去。

综上所述,LZ为什么不用数据库?。。。呵呵
回复 点赞
发动态
发帖子
C#
创建于2007-09-28

8.5w+

社区成员

64.0w+

社区内容

.NET技术 C#
社区公告
暂无公告