#c# #xml-serialization #xmlserializer #assembly-resolution
#c# #xml-сериализация #xmlserializer #сборка-разрешение
Вопрос:
У меня есть приложение, которое внедряет (через BuildAction: Embedded Resource) сборку, на которую ссылается (называемую ClassLibrary1), внутрь себя и загружает ее в AppDomain.CurrentDomain.Событие AssemblyResolve. Основная сборка определяет класс Class1:
public class Class1
{
public Class2 MyField { get; set; }
}
Оно имеет свойство типа Class2, определенное в ClassLibrary1.
Определение Class2:
public class Class2
{
public int A { get; set; }
}
В основном методе я создаю новый XmlSerializer(typeof(Class1)):
static void Main()
{
SubscribeAssemblyResolver();
MainMethod();
}
private static void MainMethod()
{
XmlSerializer xs2 = new XmlSerializer(typeof(Class1));
Class1 cl = new Class1();
}
При выполнении программы я получаю следующую ошибку:
Не удается сгенерировать временный класс (результат =1). ошибка CS0012: тип ‘ClassLibrary1.Class2’ определен в сборке, на которую нет ссылки. Необходимо добавить ссылку на сборку ‘ClassLibrary1, Version=1.0.0.0, Culture = neutral, PublicKeyToken=c06f123f2868e8c8’. ошибка CS0266: не удается неявно преобразовать тип ‘object’ в ‘ClassLibrary1.Class2’. Существует явное преобразование (вы пропускаете приведение?)
Есть идеи?
Остальная часть кода:
private static void SubscribeAssemblyResolver()
{
AppDomain.CurrentDomain.AssemblyResolve = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
static Dictionary<String, Assembly> _assemblies = new Dictionary<String, Assembly>(StringComparer.OrdinalIgnoreCase);
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return ResolveAssembly(args.Name);
}
private static Assembly ResolveAssembly(string argsName)
{
Assembly dll;
var name = "WindowsFormsApplication1.Libs." new AssemblyName(argsName).Name ".dll";
if (!_assemblies.TryGetValue(name, out dll))
{
Assembly res = typeof(Program).Assembly;
using (var input = res.GetManifestResourceStream(name))
{
if (input == null)
{
//TODO: log
return null;
}
Byte[] assemblyData = new Byte[input.Length];
input.Read(assemblyData, 0, assemblyData.Length);
if (null == (dll = Assembly.Load(assemblyData)))
{
//TODO: log
return null;
}
//TODO: log
_assemblies[name] = dll;
return dll;
}
}
return dll;
}
ОБНОВЛЕНИЕ: Создана ОШИБКА на сайте Microsoft Connect. Вы также можете загрузить образец решения visual stuido 2010 (просто разверните Details fieldgroup) оттуда, чтобы воспроизвести его.
Комментарии:
1. Эта ошибка не ограничивается динамически загружаемыми сборками. Смотрите Ошибку создания XML-сериализатора в конкретной ситуации в VB . К сожалению, ошибки XmlSerializer не исправляются, если они не критичны для безопасности.
2. Спасибо за ссылку, Джон! К сожалению, указанное решение в обходных путях для упомянутой ошибки в моем случае не работает: XmlSerializer xs2 = new XmlSerializer(typeof(Class1), new Type[] { typeof(Class2) }); Я все еще получаю те же ошибки.
3. хорошо, возможно, причина, по которой обходной путь не работает для вас, заключается в вашей динамической загрузке. В качестве эксперимента, можете ли вы попробовать это со статической загрузкой и 1) посмотреть, по-прежнему ли вы получаете ошибку, и 2) посмотреть, работает ли обходной путь для вас со статической загрузкой. Если проблема заключается в динамической загрузке, тогда я предлагаю вам ввести новую статью Connect и опубликовать URL здесь.
4. @John: проблем нет, если он загружен статически (например ClassLibrary1.dll находится в той же папке, что и ConsoleApplication1, и загружается с помощью стандартного механизма .net) Я создал новую ошибку подключения и добавил решение vs2010, ссылка на него есть в сообщении.
Ответ №1:
Я решил аналогичную проблему, сохранив сборку во временной папке
public static byte[] ReadFully(Stream input)
{
var buffer = new byte[16 * 1024];
using (var ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
public App()
{
AppDomain.CurrentDomain.AssemblyResolve = (sender, args) =>
{
var assemblyName = new AssemblyName(args.Name);
if (assemblyName.Name != "Omikad.Core")
return null;
var resourceName = "Terem." assemblyName.Name ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (stream == null)
return null;
var assemblyData = ReadFully(stream);
var tmp = Path.Combine(Path.GetTempPath(), "Omikad.Core.dll");
File.WriteAllBytes(tmp, assemblyData);
return Assembly.LoadFrom(tmp);
}
};
}
Ответ №2:
Попробуйте добавить атрибут:
[XmlInclude(typeof(Class2))]
public class Class1
{
public Class2 MyField { get; set; }
}
Комментарии:
1. Я уже пробовал это, и это не работает, но спасибо за ответ.
Ответ №3:
На данный момент я получил два несколько неудачных решения:
- Хотя вы не можете создать экземпляр XmlSerializer для типа Class1, вы все еще можете создать его экземпляр для типа Class2 из основной сборки. Это означает, что если вы переместите Class1 в ClassLibrary1 или Class2 в основную сборку — она десериализуется без ошибок. Это работает, но использовать это решение повсеместно невозможно, плюс это идеологически неверно.
- Используйте ILMerge, чтобы объединить эти сборки в одну. Но это работает только для материалов, отличных от wpf, плюс вы должны управлять ситуацией с атрибутами сборок (могут быть конфликты).
И одна очень плохая идея:
- Сгенерировать ClassLibrary1.XmlSerializer.dll с sgen.exe.
- Также внедрите его в основную сборку.
- Явно загрузите его в кэш XmlSerializer, вызывая один из его внутренних методов через отражение.
Хотя мне пришлось использовать решение номер один на данный момент, я им не удовлетворен, потому что оно слишком стесняет.
Ответ №4:
Я бы попробовал конструктор XmlSerializer(Type, Тип[]) и предоставил Class2 в качестве дополнительного типа, используя второй параметр. У меня мало опыта работы с XmlSerializer, но для DataContractSerializer это делает свое дело.