反序列化具有在 AssemblyResolve 上加载的其他程序集中声明的类型字段的对象时出错

发布于 2024-11-07 21:16:17 字数 2776 浏览 0 评论 0原文

我有一个应用程序,它在自身内部嵌入(通过 BuildAction:嵌入式资源)引用的程序集(称为 ClassLibrary1)并将其加载到 AppDomain.CurrentDomain.AssemblyResolve 事件上。 主程序集定义了一个类 Class1:

public class Class1
{        
    public Class2 MyField { get; set; }    
}

它具有在 ClassLibrary1 中定义的 Class2 类型的属性。 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;
    }

更新:创建了BUG连接站点。您还可以从那里下载示例 Visual Stuido 2010 解决方案(只需展开“详细信息”字段组)来重现它。

I have an application which embedes (via BuildAction: Embedded Resource) referenced assembly (called ClassLibrary1) inside itself and loads it on AppDomain.CurrentDomain.AssemblyResolve event.
Main assembly defines a class Class1:

public class Class1
{        
    public Class2 MyField { get; set; }    
}

It has a property of type Class2 defined in ClassLibrary1.
Definition of Class2:

public class Class2
{
    public int A { get; set; }
}

In the main method I`m creating a new XmlSerializer(typeof(Class1)):

    static void Main()
    {
        SubscribeAssemblyResolver();
        MainMethod();
    }

    private static void MainMethod()
    {
        XmlSerializer xs2 = new XmlSerializer(typeof(Class1));
        Class1 cl = new Class1();
    }

While executing a programm I get the following error:

Unable to generate a temporary class (result=1).
error CS0012: The type 'ClassLibrary1.Class2' is defined in an assembly that is not referenced. You must add a reference to assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c06f123f2868e8c8'.
error CS0266: Cannot implicitly convert type 'object' to 'ClassLibrary1.Class2'. An explicit conversion exists (are you missing a cast?)

Any ideas?

The rest of the code:

    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;
    }

UPDATE: Created a BUG on the microsoft Connect site. You can also download a sample visual stuido 2010 solution (just expand Details fieldgroup) from there to reproduce it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

清风夜微凉 2024-11-14 21:16:17

我通过将程序集保存在临时文件夹中解决了类似的问题

    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);
            }
        };
    }

I've solved similar problem by saving assembly in temporary folder

    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);
            }
        };
    }
甚是思念 2024-11-14 21:16:17

尝试添加属性:

[XmlInclude(typeof(Class2))]
public class Class1
{        
   public Class2 MyField { get; set; }    
}

Try to add atribute:

[XmlInclude(typeof(Class2))]
public class Class1
{        
   public Class2 MyField { get; set; }    
}
红焚 2024-11-14 21:16:17

就目前而言,我最终得到了两个有些糟糕的解决方案:

  1. 虽然您无法为 Class1 类型实例化 XmlSerializer,但您仍然可以从主程序集中为 Class2 类型实例化它。这确实意味着,如果您将 Class1 移动到 ClassLibrary1 或将 Class2 移动到主程序集 - 它将反序列化而不会出现错误。它有效,但不可能在所有地方都使用这个解决方案,而且它在意识形态上是错误的。
  2. 使用 ILMerge 将这些程序集合并为一个。但它仅适用于非 wpf 内容,而且您应该使用程序集属性来管理这种情况(可能会发生冲突)。

还有一个非常糟糕的想法:

  1. 使用 sgen.exe 生成 ClassLibrary1.XmlSerializer.dll。
  2. 还将其嵌入主组件中。
  3. 通过反射调用其内部方法之一将其显式加载到 XmlSerializer 缓存。

虽然我现在必须使用第一个解决方案,但我对此不满意,因为它太受限了。

As for now I`ve ended up with two somewhat bad solutions:

  1. While you can`t instanciate XmlSerializer for the type Class1, you still can instanciate it for the type Class2 from the main assembly. That does mean that if you move Class1 to ClassLibrary1 or Class2 to the main assembly - it will deserialize without errors. It works, but it is not possible to use this solution everywhere, plus it is ideologically wrong.
  2. Use ILMerge to merge those assemblies into one. But it only works for non-wpf stuff, plus you should manage the situation with the assemblies attributes (there could be conflicts).

And one very bad idea:

  1. Generate ClassLibrary1.XmlSerializer.dll with sgen.exe.
  2. Also embed it into the main assembly.
  3. Explicitly load it to the XmlSerializer cache calling one of it`s internal methods via reflection.

Although I had to use solution number one for now, I`m not satisfied with it, because it is too constraining.

月下伊人醉 2024-11-14 21:16:17

我会尝试 XmlSerializer(Type, Type[]) 构造函数,并使用第二个参数提供 Class2 作为附加类型。我对 XmlSerializer 的经验很少,但对于 DataContractSerializer 来说,这可以解决问题。

I'd try the XmlSerializer(Type, Type[]) constructor and provide Class2 as an additional type using the second parameter. I've few experience with the XmlSerializer, but for DataContractSerializer this does the trick.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文