BinaryFormatter.Deserialize не может найти сборку после ILMerge

У меня есть решение C # со ссылочной dll (также C # с той же версией .Net). Когда я создаю решение и запускаю полученный exe, без объединения exe и указанной библиотеки DLL, все работает нормально.

Теперь я хочу объединить их в один exe. Я запускаю ILMerge, и все работает нормально. Я пытаюсь запустить exe, и, похоже, он работает нормально, пока не попытается десериализовать объект, определенный в указанной dll.

using (Stream fstream = new FileStream(file_path, FileMode.Open))
{
    BinaryFormatter bf = new BinaryFormatter();
    return bf.Deserialize(fstream) as ControlledRuleCollection; 
    // throws unable to find assembly exception
}

Может быть, мне здесь не хватает какой-то опции ILMerge?


person Mike Park    schedule 02.03.2011    source источник
comment
Был ли объект сериализован из сборок до или после слияния?   -  person Lasse V. Karlsen    schedule 02.03.2011
comment
Ненавижу это говорить, но это еще одна причина, по которой я всегда пытаюсь отговорить людей от использования BinaryFormatter. Эта слишком тесная связь с метаданными типа причиняет огромную боль. Я могу порекомендовать лучшие варианты, если вы хотите избежать этого (и многих других болезненных сценариев) в будущем.   -  person Marc Gravell    schedule 02.03.2011
comment
Вы ведь знаете, что двоичная сериализация не должна быть форматом хранения, верно? Это только временный транспортный формат, слишком много ограничений на долгосрочное хранение объектов, когда это делается через двоичный сериализатор. Обновите вашу программу и до свидания старые файлы.   -  person Lasse V. Karlsen    schedule 02.03.2011
comment
Дело принято. Я вообще не рассматривал долгосрочное хранилище, но это выглядело настолько заманчиво простым для простой утилиты, которую я пишу. Ну что ж.   -  person Mike Park    schedule 02.03.2011
comment
@ Лассе хорошо сказано; но могу ли я просто подчеркнуть, что проблема заключается в BinaryFormatter, а не в двоичной сериализации в целом. Конечно, очень возможно (кашля) написать сериализатор с хорошим поведением, который будет производить двоичный вывод.   -  person Marc Gravell    schedule 02.03.2011
comment
@Marc О, ты знаешь такую ​​библиотеку? :) Да, я знаю, основная проблема с классом BinaryFormatter заключается в том, что он встраивает полностью определенное имя типа для каждого типа, которое включает имя сборки, версию и открытый ключ, если он присутствует (я могу ошибаться в части открытого ключа ). По сути, если вы увеличиваете версию своей сборки, все объекты, сериализованные с помощью предыдущей версии сборки, в основном недействительны. Но, как говорит @Marc, есть двоичные сериализаторы, которые ведут себя лучше, например Protobuf :) Так что @Marc прав, надо было быть точнее :)   -  person Lasse V. Karlsen    schedule 05.03.2011


Ответы (9)


Похоже, вы сериализовали объект внутри DLL, затем объединили все сборки с ILMerge и теперь пытаетесь десериализовать этот объект. Это просто не сработает. Процесс десериализации для двоичной сериализации будет пытаться загрузить тип объекта из исходной DLL. Эта DLL не существует после ILMerge, и, следовательно, десериализация не удастся.

Процессы сериализации и десериализации должны выполняться как до, так и после слияния. Это не может быть смешано

person JaredPar    schedule 02.03.2011

Вы можете сделать это, создав и добавив подкласс SerializationBinder, который изменит имя сборки до того, как произойдет десериализация.

sealed class PreMergeToMergedDeserializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = null;

        // For each assemblyName/typeName that you want to deserialize to
        // a different type, set typeToDeserialize to the desired type.
        String exeAssembly = Assembly.GetExecutingAssembly().FullName;


        // The following line of code returns the type.
        typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
            typeName, exeAssembly));

        return typeToDeserialize;
    }
}

Затем при десериализации добавьте это в BinaryFormatter:

BinaryFormatter bf = new BinaryFormatter();
bf.Binder = new PreMergeToMergedDeserializationBinder();
object obj = bf.Deserialize(ms);
person Phil    schedule 25.01.2012
comment
Отличный ответ! Это сэкономило мне много времени! Спасибо! - person gleng; 29.04.2015

SerializationBinder также был моим решением. Но у меня есть класс в DLL, на который есть ссылка. Так что мне нужно искать во всех сборках нагрузки. Я изменил ответы bevor с параметром, если связыватель должен искать в dll.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ibKastl.Helper
{
   public static class BinaryFormatterHelper
   {
      public static T Read<T>(string filename, Assembly currentAssembly)
      {
         T retunValue;
         FileStream fileStream = new FileStream(filename, FileMode.Open);

         try
         {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Binder = new SearchAssembliesBinder(currentAssembly,true);            
            retunValue = (T)binaryFormatter.Deserialize(fileStream);
         }
         finally
         {
            fileStream.Close();
         }

         return retunValue;
      }

      public static void Write<T>(T obj, string filename)
      {
         FileStream fileStream = new FileStream(filename, FileMode.Create);
         BinaryFormatter formatter = new BinaryFormatter();
         try
         {
            formatter.Serialize(fileStream, obj);
         }
         finally
         {
            fileStream.Close();
         }
      }
   }

   sealed class SearchAssembliesBinder : SerializationBinder
   {
      private readonly bool _searchInDlls;
      private readonly Assembly _currentAssembly;

      public SearchAssembliesBinder(Assembly currentAssembly, bool searchInDlls)
      {
         _currentAssembly = currentAssembly;
         _searchInDlls = searchInDlls;
      }

      public override Type BindToType(string assemblyName, string typeName)
      {
         List<AssemblyName> assemblyNames = new List<AssemblyName>();
         assemblyNames.Add(_currentAssembly.GetName()); // EXE

         if (_searchInDlls)
         {
            assemblyNames.AddRange(_currentAssembly.GetReferencedAssemblies()); // DLLs
         }

         foreach (AssemblyName an in assemblyNames)
         {
            var typeToDeserialize = GetTypeToDeserialize(typeName, an);
            if (typeToDeserialize != null)
            {
               return typeToDeserialize; // found
            }
         }

         return null; // not found
      }

      private static Type GetTypeToDeserialize(string typeName, AssemblyName an)
      {
         string fullTypeName = string.Format("{0}, {1}", typeName, an.FullName);
         var typeToDeserialize = Type.GetType(fullTypeName);
         return typeToDeserialize;
      }
   }

}

Использование:

const string FILENAME = @"MyObject.dat";

// Serialize
BinaryFormatterHelper.Write(myObject1,FILENAME);

// Deserialize
MyObject myObject2 = BinaryFormatterHelper.Read<MyObject>(FILENAME, Assembly.GetExecutingAssembly()); // Current Assembly where the dll is referenced
person Suplanus    schedule 17.01.2017

Возможно, вы сериализовали его из отдельной сборки, а затем попытались десериализовать его с помощью другой сборки (или более новой версии той же сборки).

Некоторое обсуждение здесь

person Peter Kelly    schedule 02.03.2011

Для тех, у кого есть эта проблема, пытаясь десериализовать из другой сборки, я нашел это решение, которое мне кажется отличным с использованием небольшого класса «BindChanger» с общим пространством имен для рассматриваемого типа объекта. https://www.daniweb.com/programming/software-development/threads/339638/deserializing-in-a-different-assembly.

person Scott Jasin    schedule 01.08.2018
comment
Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если ссылка на страницу изменится. - person Enea Dume; 01.08.2018

Я нашел другое решение этой проблемы. Моя проблема, может быть, была немного другой ...

Я пытался выполнить десериализацию из той же библиотеки, которая была сериализована, но мне не удалось найти для этого нужную сборку. Я попробовал все вышеперечисленные решения, и ни одно из них не помогло.

Я нашел решение на другом веб-сайте (Здесь)

Короче говоря, переопределите ResolveEventHandler AppDomain.CurrentDomain.AssemblyResolve в + tor

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

Затем реализуйте метод CurrentDomain_AssemblyResolve следующим образом:

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
  System.Reflection.Assembly ayResult = null;
  string sShortAssemblyName = args.Name.Split(',')[0];
  System.Reflection.Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
  foreach (System.Reflection.Assembly ayAssembly in ayAssemblies)
  {
      if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0])
      {
          ayResult = ayAssembly;
          break;
      }
  }
  return ayResult;
}

Это исправило ошибку, которая позволила мне десериализовать.

person Johan Vermaak    schedule 12.03.2020

Если вы объединяете сборки в существующую (например, все библиотеки DLL в EXE), вы можете использовать решение, предложенное в этот ответ:

// AssemblyInfo.cs for My.exe
[assembly: TypeForwardedTo(typeof(SomeTypeFromMergedDLL))]

По крайней мере, это работает для десериализации перед слиянием. IL-Merge тоже проходит; даже если вы не можете скомпилировать с переадресацией типов до типа той же сборки ...

Я не пробовал, работает ли сериализация после слияния. Но я буду держать свой ответ в курсе.

person Lars Corneliussen    schedule 05.08.2011

У меня была ситуация, когда сериализованные данные хранились на SQL-сервере старой службой .NET, которая использовалась годами. Мне нужно было получить данные из SQL, и я тоже столкнулся с этим. Я мог сослаться на .exe и заставить его работать, пока я не использовал решение, упомянутое выше. Однако имена моих сборок были другими.

sealed class Version1ToVersion2DeserializationBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            Type typeToDeserialize = null;

            // For each assemblyName/typeName that you want to deserialize to a different type, set typeToDeserialize to the desired type.
            String assemVer1 = assemblyName;
            String typeVer1 = typeName;

            if (assemblyName == assemVer1 && typeName == typeVer1)
            {
                // To use a type from a different assembly version, change the version number.
                assemblyName = Assembly.GetExecutingAssembly().FullName;
                // To use a different type from the same assembly, change the type name.
                typeName = "projectname.typename";
            }

            // The following line of code returns the type.
            typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
            return typeToDeserialize;
        }
    }
person ValiRossi    schedule 30.07.2013

у меня есть решение

   sealed class VersionDeserializationBinder : SerializationBinder
  {
     public override Type BindToType(string assemblyName, string typeName)
    {
    Type typeToDeserialize = null;
    string currentAssemblyInfo = Assembly.GetExecutingAssembly().FullName;

    //my modification
    string currentAssemblyName = currentAssemblyInfo.Split(',')[0];
    if (assemblyName.StartsWith(currentAssemblyName))assemblyName = currentAssemblyInfo;

    typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName));
    return typeToDeserialize;
}

}

Проблема десериализации: ошибка при десериализации из другой версии программы < / а>

person sandeepmaurya    schedule 16.01.2014