#c# #.net #serialization #binaryformatter
#c# #.net #сериализация #двоичный форматировщик
Вопрос:
Я пытаюсь заставить BinaryFormatter
работать в разных версиях моей сборки. Фактический класс, в который я хочу десериализовать, точно такой же в каждой версии сборки, но при десериализации, поскольку сериализуемые объекты включают имя сборки, из которой они были получены, BinaryFormatter
жалуется, что не может найти правильную сборку. Итак, я создал пользовательский, SerializationBinder
который указывает BinaryFormatter
всегда выполнять десериализацию до текущей версии сборки.
Моя схема работает и может корректно десериализовывать объекты, но она не работает, если мой объект представляет собой список T, где T был типом, сериализованным из более старой версии моей сборки.
Есть ли какой-нибудь способ заставить это работать со списками и другими универсальными типами, где параметром типа является класс из моей сборки?
//the object i want to deserialize
class MyObject
{
public string Name{get;set;}
}
//my binder class
class MyBinder : SerializationBinder
{
static string assemblyToUse = typeof (MyObject).Assembly.FullName;
public override Type BindToType(string assemblyName, string typeName)
{
var isMyAssembly = assemblyName.StartsWith("oldAssemblyName");
var assemblyNameToUse = isMyAssembly ? assemblyToUse : assemblyName;
var tn = typeName ", " assemblyNameToUse;
return Type.GetType(tn);
}
}
//my deserialize method
static object BinaryDeserialize(string input)
{
var arr = Convert.FromBase64String(input);
var ms = new MemoryStream(arr);
ms.Seek(0, SeekOrigin.Begin);
var bf = new BinaryFormatter();
bf.Binder = new MyBinder();
var obj = bf.Deserialize(ms);
return obj;
}
static void Test()
{
//this works
//serialized(new MyObject());
var str = ReadSerialized_MyObject();
var obj = BinaryDeserialize(str);
//this doesn't work
//serialized(new List<MyObject>());
var str2 = ReadSerialized_List_of_MyObject();
var obj = BinaryDeserialize(str2);
}
Ответ №1:
Если вы сериализовали экземпляр List< MyClass> из вашей сборки версии 1.0.0.0, SerializationBinder.Функции BindToType будет предложено предоставить этот тип:
System.Collections.Generic.List`1[[MyAssembly.MyClass, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=12345678901234567890]]
Чтобы переназначить тип List< MyClass > в вашу сборку версии 2.0.0.0, вам необходимо изменить имя типа на это:
System.Collections.Generic.List`1[[MyAssembly.MyClass, MyAssembly]]
Главное, на что следует обратить внимание, это то, что имя сборки указано не полностью. Если вы попытаетесь полностью указать имя сборки с номером версии 2.0.0.0, это не сработает.
Комментарии:
1. это то, что я в итоге сделал… указывает на вас, так как я забыл ответить на это сам
2. Это просто может спасти мне жизнь. Размер сообщения, отправляющего более 1000 списков, составляет около 3 МБ после того, как оно было обработано с использованием конечной точки базовой службы WCF привязки HttpBinding. Теперь, когда я вручную кодирую сообщение в двоичном формате, уменьшите его примерно до 4 МБ. Вы спасли мою жизнь!
Ответ №2:
Приложение A создает сериализованный двоичный файл форматирования «SerializedList.bin», который содержит список (результат), где результатом является сериализуемый объект. Теперь приложение B хочет десериализовать файл и загрузить в объект List (результат). Вот как я заставил это работать..
Имя сборки приложения A — «Сериализовать»
Имя сборки приложения B — «десериализовать»
Применение кода (сериализация):
namespace Serialize
{
class Program
{
static void Main(string[] args)
{
List<Result> result = ;//Get From DB
IFormatter formatter = new BinaryFormatter();
Stream sStream = new FileStream(
"SerializedList.bin",
FileMode.CreateNew,
FileAccess.Write,
FileShare.None);
formatter.Serialize(sStream, result);
sStream.Close();
}
}
}
Некоторый результирующий объект:
[Serializable]
public class Result
{
public decimal CONTACT_ID { get; set; }
public decimal INSTITUTION_NBR { get; set; }
}
Код приложения B (десериализация):
namespace DeSerialize
{
class Program
{
static void Main(string[] args)
{
IFormatter formatter = new BinaryFormatter();
string fromTypeName = "System.Collections.Generic.List`1[[Serialize.Result, Serialize, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]";
string fromTypeName1 = "Serialize.Result";
string toTypename = "System.Collections.Generic.List`1[DeSerialize.Result]";
string toTypename1 = "DeSerialize.Result";
string toTypeAssemblyName = Assembly.GetExecutingAssembly().FullName;
DictionarySerializationBinder dic = new DictionarySerializationBinder();
dic.AddBinding(fromTypeName, toTypename);
dic.AddAssemblyQualifiedTypeBinding(fromTypeName1, toTypename1, toTypeAssemblyName);
formatter.Binder = dic;
Stream dStream = new FileStream(
"SerializeList.bin",
FileMode.Open,
FileAccess.Read,
FileShare.Read);
List<Result> listDS =
(List<Result>)formatter.Deserialize(dStream);
dStream.Close();
}
}
sealed class DictionarySerializationBinder : SerializationBinder
{
Dictionary<string, Type> _typeDictionary = new Dictionary<string, Type>();
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToReturn;
if (_typeDictionary.TryGetValue(typeName, out typeToReturn))
{
return typeToReturn;
}
else
{
return null;
}
}
public void AddBinding(string fromTypeName, string toTypeName)
{
Type toType = Type.GetType(toTypeName);
if (toType == null)
{
throw new ArgumentException(string.Format(
"Help, I could not convert '{0}' to a valid type.", toTypeName));
}
_typeDictionary.Add(fromTypeName, toType);
}
public void AddAssemblyQualifiedTypeBinding(string fromTypeName, string toTypeName, string toTypeAssemblyName)
{
Type typeToSerializeTo = GetAssemblyQualifiedType(toTypeAssemblyName, toTypeName);
if (typeToSerializeTo == null)
{
throw new ArgumentException(string.Format(
"Help, I could not convert '{0}' to a valid type.", toTypeName));
}
_typeDictionary.Add(fromTypeName, typeToSerializeTo);
}
private static Type GetAssemblyQualifiedType(string assemblyName, string typeName)
{
return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
}
}
}
Ответ №3:
Просто обработайте AppDomain.AssemblyResolve
событие и верните нужную сборку при вызове Type.GetType
метода. Вот так просто!
Ответ №4:
Вы также можете использовать более простую конструкцию, подобную этой, в функции BindToType:
var tn = typeof(List<MyClass>).AssemblyQualifiedName;
return Type.GetType(tn, true)
Последний параметр ‘true’ выдаст вам сообщение об ошибке (с трассировкой), если ваше определение типа неверно. Это сэкономит вам много времени при последующей отладке.