InvalidCastException при сериализации и десериализации
У меня есть этот код:
public byte[] SerializeToBlob()
{
using (var buffer = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(buffer, this);
buffer.Position = 0;
return buffer.ToArray();
}
}
public static ActionData DeserializeFromBlob(byte[] state)
{
using (var buffer = new MemoryStream(state))
{
var formatter = new BinaryFormatter();
var result = formatter.Deserialize(buffer);
return (ActionData) result;
}
}
И я вызываю его следующим образом:
byte[] actionDataBlob = ad.SerializeToBlob();
var ad1 = ActionData.DeserializeFromBlob(actionDataBlob);
Однако, я получаю InvalidCastException, когда он пытается передать десериализованный объект в его тип:
[A] ActionData не может быть отброшена [B] ActionData. Тип А берется из 'XXXX.XXXX.Auditing, Version = 1.0.76.0, Культура = нейтральная, PublicKeyToken = null ' в контексте "По умолчанию" в месте "C:\Users\Craig\AppData\Local\Temp\Temporary ASP.NET Файлы\корень\5d978e5b\ffc57fe1\сборка\DL3\2b1e5f8f\102c846e_9506ca01\XXXX.XXXX.Auditing.DLL. Тип B берется из 'XXXX.XXXX.Auditing, Version = 1.0.76.0, Культура = нейтральная, PublicKeyToken = null ' в контексте" LoadNeither "в местоположение 'F:\Visual Studio Проекты\XXXXXXXXX\источник\XXXX.XXXX.SilverlightClient.Web\Bin\XXXX.XXXX.Auditing.dll".
(XXXX.XXXX есть, чтобы затенять имя клиента)
Что дает?
Я задал здесь следующий вопрос:
Как я могу сериализовать некоторые простые данные аудита для хранения в таблице SQL?
Ответы
Ответ 1
Вы загрузили одну и ту же сборку дважды, в разные контексты загрузчика. Например. вы сначала загрузили XXX.Auditing с помощью Assembly.LoadFrom()
, а затем какую-то другую (или вашу) сборку загрузили нормально. Фактически, двоичный десериализатор может быть тем, кто загрузил сборку во второй раз, хотя я бы не знал, почему (нет опыта работы с ASP.NET).
Ответ 2
Мне кажется, что у вас одинаковый класс в разных сборках (или в веб-приложениях). BinaryFormatter включает метаданные типа в сериализации, что означает, что будет выполняться только одна и та же сборка. 2 решения:
- поместите этот тип в dll и укажите, что одиночная dll в обоих местах
- используйте сериализатор на основе контрактов
Лично я бы выбрал второе из-за большого количества причин, не ограничиваясь этим. Вероятные варианты:
- XmlSerializer (xml; сериализует общедоступные поля и свойства; только дерево)
- DataContractSerializer (xml; сериализует отмеченные поля и свойства (общедоступные или частные), "дерево" или "график" )
- protobuf-net (двоичный; сериализует отмеченные поля и свойства (публичные или частные), только "дерево"
Что лучше всего зависит от сценария.
Ответ 3
Ok. Я только что столкнулся с этой проблемой.
В моем случае проблема состояла в том, что я загружал сборку из массива байтов (я использую модель плагина, поэтому это довольно распространенное использование) и десериализует объект, но он не будет отличать его с тем же сообщение как оригинальный вопрос.
Сначала я подумал, что это просто из-за версии serializer и dll и т.д., поэтому я написал свой собственный сериализатор и снова столкнулся с той же проблемой.
Проблема действительно возникла из создания типа. В моих процедурах десериализации я использовал знакомый метод Type.GetType(string) и передавал AssemblyQualifiedName, которое действительно работает, и для всех типов, которые находятся вне mscorlib, требуется.
Ну, получается, что GetType не идет по списку загруженных сборок, чтобы попытаться найти совпадение, но оставит его до преобразователя fusion.
Это означает, что любой тип, который существует в сборке, который был загружен в любом контексте, отличном от "Загрузить" (ака "По умолчанию" в сообщении об исключении), не найден, и GetType пытается загрузить сборку в обычном режиме.
В моем случае это поведение вызвало загрузку 2 экземпляров сборки в appdomain: on из моего байтового массива, а другой с диска, найденного с помощью fusion.
Я решил эту проблему, перечислив загруженные сборки в моем appdomain, ища подходящее имя сборки (проанализированное из моего AssemblyQualifiedName). После того, как я нашел, я создал свой тип (минус информация сборки), используя эту конкретную сборку (Assembly.GetType(String)).
Вот код, который позволил мне преодолеть эту проблему:
Dim ot As System.Type
Dim tname = "MyType"
Dim aname = "MyPlugin"
'// The following lambda expression returns only assemblies that match my assembly name
'// Your assembly name could be a fully qualified name or just the simple assembly name
'// I chose to use the simple name so that i didn't have to store unnecessary data and because i didn't want version specific info
Dim asms = AppDomain.CurrentDomain.GetAssemblies().Where(Function(__) __.GetName.Name.Equals(aname))
'If there is only one assembly loaded...use it
If asms.Count = 1 Then
ot = asms(0).GetType(tname)
'// If there are multiple assemblies loaded (with the same name), i'm picking the one that is loaded from disk, if such is available, otherwise default back to the first one that was loaded into the appdomain
'// If you do have multiple assemblies loaded, it because you (or .NET) has loaded them in different contexts. You might need to adjust for which context you want. I suppose you could pass the desired context in as a parameter and look for it
ElseIf asms.Count > 1 Then
Dim asm = asms.FirstOrDefault(Function(__) Not String.IsNullOrEmpty(__.Location))
If asm IsNot Nothing Then
ot = asm.GetType(tname)
Else
ot = asms(0).GetType(tname)
End If
Else
'// not yet loaded...use default type resolution from Type.GetType
ot = Type.GetType(tname & "," & aname)
End If
Dim obj
'// Note that the method here is using the already resolved System.Type from above.
'// This is important because it causes Activator to create an instance from the assembly
'// that we really want and not one from fusion resolver.
obj = Activator.CreateInstance(ot)
Надеюсь, это поможет кому-то другому.
-Eriq
Ответ 4
В конце концов, моя проблема была связана с динамической загрузкой, я думаю. Когда я реализовал его с помощью XmlSerializer, у меня была точно такая же проблема.
Решение заключалось в том, чтобы поместить классы, которые я хотел бы сериализовать, в отдельную сборку, чтобы они не были динамически загружены.
Ответ 5
Im имеет ту же проблему и точно такую же ошибку, но не все, но иногда. im, используя linqtoSQL, и список данных сериализуется, а затем доступ через
filteredTasks = (List<App.Task.Entity.GetAllTasksResult>)Session["myTaskList"];
Ответ 6
У меня такая же проблема с формой Microsoft Office InfoPath с использованием .net-кода. Даже собственный код Microsoft справился с этой проблемой.
Да, я вижу, что это загрузка из двух разных мест одновременно.
\ AppData\Local\сборка\DL3...
а также
\ AppData\Local\Micorosoft\InfoPath\FormCache4...
вздыхать