InvalidCastException при сериализации и десериализации - PullRequest
13 голосов
/ 17 июля 2009

У меня есть этот код:

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, версия = 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, версия = 1.0.76.0, Культура = нейтральная, PublicKeyToken = null ' в контексте «LoadNeither» в расположение 'F: \ Visual Studio Проекты \ XXXXXXXXX \ источник \ XXXX.XXXX.SilverlightClient.Web \ Bin \ XXXX.XXXX.Auditing.dll».

(XXXX.XXXX скрывает имя клиента)

Что дает?

Теперь я задал связанный вопрос здесь:

Как мне сериализовать некоторые простые данные аудита для хранения в таблице SQL?

Ответы [ 6 ]

4 голосов
/ 17 июля 2009

Вы загрузили одну и ту же сборку дважды, в разных контекстах загрузчика . Например. вы случайно загрузили XXX.Auditing с Assembly.LoadFrom(), а затем какая-то другая (или ваша) сборка загрузила его нормально. Фактически, двоичный десериализатор мог быть тем, кто загружал сборку во второй раз, хотя я не знаю почему (без опыта работы с ASP.NET).

4 голосов
/ 17 июля 2009

Мне кажется, что у вас один и тот же класс в разных сборках (или веб-приложениях). BinaryFormatter включает метаданные типа в сериализацию, что означает, что будет делать только та же самая сборка . 2 решения:

  • поместите этот тип в dll и укажите, что single dll в обоих местах
  • использовать контрактный сериализатор

Лично я выбрал бы второе по огромному количеству причин, не ограничивающихся только этим. Возможные варианты:

  • XmlSerializer (xml; сериализует открытые поля и свойства; только "дерево")
  • DataContractSerializer (xml; сериализует помеченные поля и свойства (общедоступные или частные); "дерево" или "граф")
  • protobuf-net (двоичный файл; сериализует помеченные поля и свойства (общедоступные или частные); только "дерево")

Что лучше всего зависит от сценария.

2 голосов
/ 21 мая 2012

Хорошо. Я только что столкнулся с этой же проблемой.

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

Сначала я подумал, что это только из-за сериализатора и dll-версий и тому подобного ... поэтому я написал свой собственный сериализатор и снова столкнулся с той же проблемой.

Проблема действительно возникла из-за создания типа. В моих процедурах десериализации я использовал знакомый метод Type.GetType (string) и передавал AssemblyQualifiedName, который работает и для всех типов, которые находятся за пределами mscorlib, это требуется. Что ж, получается, что GetType не просматривает список загруженных сборок, чтобы попытаться найти совпадение, но оставляет его на усмотрение Fusion.

Это означает, что любой тип, который существует в сборке, которая была загружена в любом контексте, кроме «Загрузить» (иначе «По умолчанию» в сообщении об исключении), не найден, и GetType пытается загрузить сборку обычным образом.

В моем случае это привело к загрузке 2 экземпляров сборки в домене приложения: из моего байтового массива, а другой - с диска, найденного с помощью fusion.

Я решил эту проблему путем перечисления загруженных сборок в моем домене приложения и поиска подходящего имени сборки (проанализированного из моего 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's 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's resolver.
obj = Activator.CreateInstance(ot)

Надеюсь, это поможет кому-то еще.

-Eriq

1 голос
/ 17 июля 2009

В конце концов, моя проблема была с динамической загрузкой, я думаю. Когда я реализовал его с помощью XmlSerializer, у меня возникла точно такая же проблема.

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

0 голосов
/ 13 января 2014

У меня такая же проблема с формой Microsoft Office InfoPath с использованием кода .net. Даже собственный код Microsoft столкнулся с этой проблемой.

Да, я вижу, что он загружается из двух разных мест одновременно .. \ AppData \ Local \ сборка \ DL3 ...

, а также

\ AppData \ Local \ Micorosoft \ InfoPath \ FormCache4 ...

вздыхает

0 голосов
/ 09 ноября 2010

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

filteredTasks = (List<App.Task.Entity.GetAllTasksResult>)Session["myTaskList"];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...