Привязка сериализации к списку

#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 (результат). Вот как я заставил это работать..

Ссылка: http://social.msdn.microsoft.com/forums/en-US/netfxremoting/thread/eec2b7a6-65f8-42d1-ad4f-409f46bdad61

Имя сборки приложения 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’ выдаст вам сообщение об ошибке (с трассировкой), если ваше определение типа неверно. Это сэкономит вам много времени при последующей отладке.