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 cannot be cast to [B]ActionData. Type A originates from 'XXXX.XXXX.Auditing, Version=1.0.76.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\Craig\AppData\Local\Temp\Temporary ASP.NET Files\root\5d978e5b\ffc57fe1\assembly\dl3\2b1e5f8f\102c846e_9506ca01\XXXX.XXXX.Auditing.DLL'. Type B originates from 'XXXX.XXXX.Auditing, Version=1.0.76.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' at location 'F:\Visual Studio Projects\XXXXXXXXX\source\XXXX.XXXX.SilverlightClient.Web\bin\XXXX.XXXX.Auditing.dll'.

(XXXX.XXXX существует, чтобы скрыть имя клиента)

Что дает?

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

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

 Craig Shearer17 июл. 2009 г., 09:32
@Marc - две вызывающие линии - т.е. byte [] actionDataBlob = ad.SerializeToBlob (); var ad1 = ActionData.DeserializeFromBlob (actionDataBlob);
 Craig Shearer17 июл. 2009 г., 09:24
Да, это то, что удивило меня. Ничего между двумя линиями.
 Jon Skeet17 июл. 2009 г., 09:13
Эти звонки действительно рядом друг с другом, в одной программе?
 Kane17 июл. 2009 г., 09:28
Я так понимаю, вы пытались очистить ваши временные файлы ASP.NET.
 Marc Gravell♦17 июл. 2009 г., 09:33
И & quot; объявление & quot; такое экземпляр ActionData? причудливо.

Ответы на вопрос(6)

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

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

Сначала я подумал, что это только из-за сериализатора и версий 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

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

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

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

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

а также

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

sigh

Решение Вопроса

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

 17 июл. 2009 г., 11:26
Тогда должен быть кто-то еще, кто загружает эту сборку в «По умолчанию». контекст. Вы можете попробовать подключить некоторые события загрузки сборки, чтобы узнать, кто что загружает.
 Craig Shearer17 июл. 2009 г., 09:46
Ах, на самом деле, в этом есть какой-то смысл. Аудит DLL на самом деле загружается ORM динамически - он, вероятно, использует Assembly.LoadFrom () для его загрузки. Но это сборка, которая использует BinaryFormatter для его десериализации, поэтому я не понимаю, почему она будет загружаться снова. Но тогда у меня нет большого опыта использования BinaryFormatter, поэтому я точно не могу быть уверен.

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

put this type in a dll, and reference that single dll in both places use a contract-based serializer

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

XmlSerializer (xml; serializes public fields and properties; "tree" only) DataContractSerializer (xml; serializes marked fields and properties (public or private); "tree" or "graph") protobuf-net (binary; serializes marked fields and properties (public or private); "tree" only)

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

 17 июл. 2009 г., 11:25
О, хорошо, ты прав, но это немного придирчиво :)
 Craig Shearer17 июл. 2009 г., 09:31
ОК, спасибо за совет по другим вариантам. Я не уверен, что выбрать в любом случае - на самом деле я сформулирую другой вопрос. НО, как я уже говорил в предыдущем комментарии, эти две строки кода находятся рядом друг с другом, В ЭТОЙ ЖЕ СБОРКЕ!
 17 июл. 2009 г., 09:37
В этом случае это одна и та же сборка, поскольку полное имя сборки совпадает. Но я второй XmlSerializer и т. Д. В пользу двоичной сериализации.
 17 июл. 2009 г., 09:33
В этом случае первый подход (перемещение этого объекта в dll) может быть наиболее прагматичным.
 17 июл. 2009 г., 10:10
Двоичная сериализация не является проблемой; BinaryFormatter есть ;-p Есть небольшая разница

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

filteredTasks = (List<App.Task.Entity.GetAllTasksResult>)Session["myTaskList"];

Ваш ответ на вопрос