背景
我正在尝试创建一个我在VB.NET中创建的业务对象的副本。我已经实现了ICloneable接口,并且在Clone函数中,我通过使用BinaryFormatter对其进行序列化来创建对象的副本,然后直接反序列化为我从函数返回的另一个对象。
我尝试序列化的类被标记为“Serializable”以及类中包含的子对象。
我已经通过编写类似于以下内容的代码测试了克隆方法:
Dim obj as New Sheep()
Dim dolly as Sheep = obj.Clone()
此时一切正常。
问题
我有一个自定义的Windows窗体控件,它继承自第三方控件。这个自定义控件基本上包含我想要克隆的对象(因为这个对象最终会提供第三方控件)。
我想在Windows窗体控件中创建一个对象的克隆,以便我可以允许用户操作属性,同时可以选择取消更改并将对象恢复为它们在进行更改之前的状态。我希望在用户开始进行更改之前获取对象的副本并保留它,以便在按下取消时准备就绪。
我的想法是按照以下方式编写代码:
Dim copy as Sheep = MyControl.Sheep.Clone()
然后允许用户操纵MyControl.Sheep
上的属性。但是当我尝试这样做时,clone方法抛出一个异常,说明:
在程序集“My_Assembly_Info_Here”中键入“MyControl”未标记为可序列化
在我调用BinaryFormatter.Serialize(stream,Me)
时会抛出此错误。
我尝试在MyControl
上创建一个方法,该方法返回对象的副本,并首先将MyControl.Sheep
分配给另一个变量,然后克隆变量,但似乎没有任何效果。但是,直接创建对象的新实例并克隆它可以正常工作!
任何想法,我出错了?
解决方案
马克的回答帮助我指出了正确的方向。来自Rocky Lhotka的This博客文章解释了问题以及如何解决问题。
答案 0 :(得分:2)
您是否有UI正在订阅的活动?一个{Foo} Changed事件,如果数据绑定,或者可能是INotifyPropertyChanged? 您可能必须将事件支持字段标记为[NonSerialized](或者在VB中查找属性 - 我是C#人......)。如果您正在使用类似字段的事件(即没有添加/删除的缩写语法),则使用[field:NonSerialized]标记整个事件(再次,转换为VB)。
马克
答案 1 :(得分:0)
一个显而易见的问题,但你确定你没有从你的羊对象中引用MyControl;是一个对象或列表或任何东西?如果是这种情况,这就是阻止您克隆业务对象的原因。
超过可能的候选人将是 .Parent 或 .Tag 属性。
答案 2 :(得分:0)
在第三方库中,如果某些内容未标记为可序列化,那么 应该不能被序列化,但是通常它不可序列化,因为开发人员根本就没有包含它。您可以使用反射来制作控件的公共属性的副本,并将其状态返回到取消时的反射版本。这种方法有性能影响但是因为你在UI层工作,我想它不会太担心。此方法无法保证无错误;公共属性不一定代表一个类的整个状态,并且设置一些属性可能有副作用(它们不应该,但是你没有编写代码,因此要么ILDasm它,要么看到或希望最好)。
此外,并非所有类型的属性都可以序列化,在这种情况下,您需要通过手动为这些类型(以及可能的类型属性)编写序列化例程来进一步。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
public class NonSerializableSheep
{
public NonSerializableSheep() { }
public string Name { get; set; }
public int Id { get; set; }
// public read only properties can create a problem
// with this approach if another property or (worse)
// a group of properties sets it
public int Legs { get; private set; }
public override string ToString()
{
return String.Format("{0} ({1})", Name, Id);
}
}
public static class GhettoSerializer
{
// you could make this a factory method if your type
// has a constructor that appeals to you (i.e. default
// parameterless constructor)
public static void Initialize<T>(T instance, IDictionary<string, object> values)
{
var props = typeof(T).GetProperties();
// my approach does nothing to handle rare properties with array indexers
var matches = props.Join(
values,
pi => pi.Name,
kvp => kvp.Key,
(property, kvp) =>
new {
Set = new Action<object,object,object[]>(property.SetValue),
kvp.Value
}
);
foreach (var match in matches)
match.Set(instance, match.Value, null);
}
public static IDictionary<string, object> Serialize<T>(T instance)
{
var props = typeof(T).GetProperties();
var ret = new Dictionary<string, object>();
foreach (var property in props)
{
if (!property.CanWrite || !property.CanRead)
continue;
ret.Add(property.Name, property.GetValue(instance, null));
}
return ret;
}
}
public class Program
{
public static void Main()
{
var nss = new NonSerializableSheep
{
Name = "Dolly",
Id = 12
};
Console.WriteLine(nss);
var bag = GhettoSerializer.Serialize(nss);
// a factory deserializer eliminates the additional
// declarative step
var nssCopy = new NonSerializableSheep();
GhettoSerializer.Initialize(nssCopy, bag);
Console.WriteLine(nssCopy);
Console.ReadLine();
}
}
}