XmlSerializer在构造函数中出现FileNotFoundException错误

395

我正在处理的一个应用在尝试序列化类型时出现了故障。

例如下面这样的语句

XmlSerializer lizer = new XmlSerializer(typeof(MyType));

输出:

System.IO.FileNotFoundException occurred
  Message="Could not load file or assembly '[Containing Assembly of MyType].XmlSerializers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified."
  Source="mscorlib"
  FileName="[Containing Assembly of MyType].XmlSerializers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
  FusionLog=""
  StackTrace:
       at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
       at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)

我没有为我的类定义任何特殊的序列化器。

我该如何解决这个问题?


5
好的,这个问题只是我用C#版本询问一个已经提出的VB问题: https://dev59.com/53VC5IYBdhLWcg3wcgmd 谢谢大家。 - Irwin
2
六年过去了,@VladV的答案是最简单且影响最小的解决方案。只需将“生成序列化程序集”下拉菜单更改为“启用”,而不是“自动”。 - Riegardt Steyn
@Heliac:我不同意。这种方法并不总是有效的。请看Benoit Blanchon对Vlad答案的评论。对于我来说,最简单的方法是在配置文件中不使用String.Collection。相反,我使用: string[] items = Settings.Default.StringofNewlineDelimitedItems.Split(new[] {Environment.NewLine}); - Andrew Dennison
22个回答

451

信不信由你,这是正常行为。一个异常被抛出但被 XmlSerializer 处理,所以如果你忽略它,一切都应该继续进行得很好。

我发现这非常令人恼火,如果你搜索一下,就会发现有很多关于此问题的投诉,但据我所读,微软没有计划采取任何行动。

如果您关闭特定异常的首次机会异常,您可以在调试时避免一直弹出异常窗口。 在 Visual Studio 中,转到调试 ->异常(或按Ctrl + Alt + E),公共语言运行时异常 -> System.IO -> System.IO.FileNotFoundException

您可以在博客文章C# XmlSerializer FileNotFound exception(讨论 Chris Sells 的工具 XmlSerializerPreCompiler)中找到另一种解决方法的信息。


179
解决这个问题的一种可能方法是在“工具” -> “选项” -> “调试” -> “常规选项”中勾选“仅限我的代码”选项。 - Frederic
28
@Frederic: 这条评论太棒了!我正在这里满脸“什么鬼?”的表情,试图追踪这个虚假的异常,结果发现了这个问题及其答案(这是微软的错,还有什么新鲜事吗?),但我不想禁用异常处理,因为我可能需要它来调试我的代码。A+! - Kumba
31
我认为下面Hans的建议更有价值——使用一个不会产生这个异常的不同方法调用:XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeof(MyType) })[0]; - bright
3
问题在于这不符合我的测试要求,所以我不能仅仅“忽略”这个异常。 - Csaba Toth
19
抱歉,但这个建议很糟糕。在我的经验中,FileNotFoundException是比较常见的异常之一,禁用这种异常报告只会在未来某天引发麻烦。最好开启“仅限我的代码”,或者启用下面描述的创建序列化程序集的选项。 - Quark Soup
显示剩余4条评论

116

正如Martin Sherburn所说,这是正常行为。XmlSerializer的构造函数首先尝试查找一个名为[YourAssembly].XmlSerializers.dll的程序集,该程序集应包含用于序列化您的类型的生成类。由于默认情况下不生成这样的DLL(它们不会自动生成),因此会引发FileNotFoundException异常。当发生这种情况时,XmlSerializer的构造函数会捕获该异常,并且DLL会在运行时通过XmlSerializer的构造函数自动生成(这是通过在计算机的%temp%目录中生成C#源文件,然后使用C#编译器编译它们来完成的)。对于同一类型的XmlSerializer的其他构造将只使用已经生成的DLL。

更新:从.NET 4.5开始,XmlSerializer不再执行代码生成,也不执行使用C#编译器进行编译以便在运行时创建序列化器程序集,除非通过设置配置文件设置(useLegacySerializerGeneration)明确强制要求。该更改删除了对的依赖,并提高了启动性能。来源:.NET Framework 4.5 Readme,1.3.8.1节。

XmlSerializer的构造函数会处理异常。你不需要做任何事情,只需点击“继续”(F5)以继续执行程序,一切都会很好。如果你被异常打断程序的执行并弹出异常帮助器所困扰,那么你要么关闭了“仅限我的代码”,要么将FileNotFoundException设置为在抛出时中断执行,而不是在“用户未处理”时中断执行。
要启用“仅限我的代码”,请转到“工具”>>“选项”>>“调试”>>“常规”>>“启用仅限我的代码”。要关闭在抛出FileNotFound时中断执行,请转到“调试”>>“异常”>>“查找”>>输入“FileNotFoundException”>>取消选中System.IO.FileNotFoundException下的“已抛出”复选框。

3
你的更新表明在.NET 4.5中不应该出现这个异常,但我仍然遇到了它。 - Tim Sparkles
抛出文件未找到异常两次,首先从本地路径查找“应该生成”的程序集,然后在GAC中查找,第三次正常工作。可以从进程监视器中查看;消耗资源/CPU。 - hB0
2
我只希望微软能够实现这样的代码逻辑:如果 (File.Exists(...)) { Load } else { Fallback },而不是 try { Load } catch { Fallback }。基于异常的流程控制让我的调试体验变得更加困难和脆弱,给人一种不好的感觉。 - Tim Sparkles
1
@Timbo:一个简单的 File.Exists() 可能不足以满足需求。定位程序集并不是一件简单的事情,运行时会在多个位置检查,我相信其行为会根据环境(控制台应用程序与托管在IIS中)而改变。我想应该实现一个 TryLoadAssembly() 或类似的方法。 - Allon Guralnek
很好的观点 - 昨晚我回家后考虑了一下程序集解析,并意识到File.Exists是不够的。也许我真正想要的是一个专门的异常类型AssemblyNotFoundException,这样我就可以忽略它,但仍然可以捕获实际重要的1st-chance FileNotFoundExceptions。 - Tim Sparkles
显示剩余3条评论

72
在Visual Studio项目属性中(如果我没记错的话是“生成”选项卡),有一个名为“生成序列化程序集”的选项。尝试将其打开,对于生成[MyType所在的包含程序集]的项目。

4
如果 Visual Studio 仍未生成序列化程序集,也可以参考 https://dev59.com/s3VC5IYBdhLWcg3w9GLM#8798289。 - Benoit Blanchon
1
最好、最清晰、最简明的答案!我也希望能再次点赞! - John Zabroski

66

有一个解决方法。如果你使用

XmlSerializer lizer = XmlSerializer.FromTypes(new[] { typeof(MyType) })[0];

对于这个异常,应该避免发生。以下解决方法适用于我。

警告:不要多次使用此方法,否则将会造成内存泄漏

如果您使用此方法为相同类型创建XmlSerializer实例超过一次,那么将会极度消耗内存!这是因为此方法绕过了内置缓存提供的XmlSerializer(type)XmlSerializer(type, defaultNameSpace)构造函数(所有其他构造函数也绕过了缓存)。如果您使用任何其他方法创建XmlSerializer实例,您必须自己实现缓存,否则您将会大量泄漏内存。


45
警告:如果您使用此方法多次为相同类型创建XmlSerializer实例,则会严重泄漏内存!这是因为此方法绕过了XmlSerializer(type)XmlSerializer(type, defaultNameSpace)构造函数提供的内置缓存(所有其他构造函数也绕过了缓存)。如果您使用任何不是这两个构造函数创建XmlSerializer的方法,必须实现自己的缓存,否则将大量消耗内存。 - Allon Guralnek
4
天啊,你说得对,经过进一步的反编译,我发现它确实会检查缓存,但是会在生成序列化程序集之后才这样做!真是令人困惑! - JerKimball
4
原来是一个已知的漏洞:http://weblogs.asp.net/cschittko/archive/2005/01/14/353435.aspx - JerKimball
3
@JerKimball: 那个页面实际上并没有说谎。正如你发现的那样,FromTypes 确实会填充缓存。因此,它应该是一种有效的方法来在一个语句中预热空的 XmlSerializer 缓存(就像该文章所建议的那样),但却是从它里面检索任何东西的一个非常糟糕的方式(应该只能通过最简单的构造函数来完成)。无论如何,我不知道这是一个 bug,我总是认为任何泄漏的东西都应该泄漏(比如更高级的 XmlSerializer 构造函数)。我甚至不会考虑使用 FromTypes(),因为你可以只做 types.Select(t => new XmlSerializer(t)) - Allon Guralnek
2
@AllonGuralnek 使用“FromTypes”的非探测方面确实具有吸引力-尽管抛出的所有异常都被捕获,但它是一项昂贵的操作;“自己缓存”的方法似乎是唯一的解决方法,因为唯一得到官方支持的修复看起来在一个晦涩的基于Web的程序集中。(编辑:坦率地说,我完全支持将所有内容移植到数据合同中 :)) - JerKimball
显示剩余3条评论

31
我遇到了这个问题,试过所有提到的解决方法都无效。
后来我终于找到了一种解决办法。看起来序列化器不仅需要类型,还需要嵌套的类型。 将以下内容更改:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));

到这个:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T).GetNestedTypes());

对我来说解决了问题。再也没有异常或其他问题了。


14
这对我很有用。使用.Net 4.0,格式为var xmlSerializer = new XmlSerializer(typeof(T), typeof(T).GetNestedTypes()); - user3161729
1
这对我也起作用。但它似乎只在序列化时必要,而不是反序列化时。也许这有道理,也许没有。 - SteveCinq
2
如果运行多次,这也会导致内存泄漏。 - Volodymyr Kotylo
这对我来说很有效,使用的是.NET7和Visual Studio 2022。 - rrirower

9
我的解决方案是直接使用反射来创建序列化器。这样可以避免导致异常的奇怪文件加载过程。我把这个方法打包成了一个帮助函数,还负责缓存序列化器。
private static readonly Dictionary<Type,XmlSerializer> _xmlSerializerCache = new Dictionary<Type, XmlSerializer>();

public static XmlSerializer CreateDefaultXmlSerializer(Type type) 
{
    XmlSerializer serializer;
    if (_xmlSerializerCache.TryGetValue(type, out serializer))
    {
        return serializer;
    }
    else
    {
        var importer = new XmlReflectionImporter();
        var mapping = importer.ImportTypeMapping(type, null, null);
        serializer = new XmlSerializer(mapping);
        return _xmlSerializerCache[type] = serializer;
    }
}

这里有两个问题 - 首先,你的代码不是线程安全的;其次(更重要的是),你试图复制 .net 运行时已经执行的操作(基于你使用的构造函数)。也就是说,没有必要写这段代码。 - Dave Black
@DaveBlack:是的,quadfinity使用ConcurrentDictionary进行缓存的答案会更好。 - edeboursetty
@d-b 我的第二个观点是缓存甚至不需要 - 只要使用框架缓存的两个构造函数之一即可(OP正在使用第一个)。来自MSDN:为了提高性能,XML序列化基础结构会动态生成程序集以序列化和反序列化指定类型。框架会找到并重用这些程序集。只有在使用以下构造函数时才会发生此行为: XmlSerializer.XmlSerializer(Type) XmlSerializer.XmlSerializer(Type, String) 参考资料:https://msdn.microsoft.com/zh-cn/library/system.xml.serialization.xmlserializer(v=vs.110).aspx - Dave Black
@DaveBlack:是的,但即使使用完全有效,这些构造函数也会在内部抛出和捕获异常。这非常糟糕,这也是OP首先提出问题的原因。 - edeboursetty
@d-b 是的,但我想表达的是(但不够清楚 - 对不起),你解决方案中唯一必要的行是else条件下的前三行。 - Dave Black
@DaveBlack 我已经在这个else语句中使用了这3行代码,它可以工作,我应该继续使用它还是应该使用像这里提供的解决方案?https://dev59.com/s3VC5IYBdhLWcg3w9GLM我喜欢d--b代码的一件事是它只需要我更改我的common.dll中的一个小东西,其中我有de serialize和deserialze方法,而不必更改所有项目的csproj。 - Vincent

8
为了避免异常,您需要做两件事情:
  1. 向序列化类添加属性(希望您能够访问)
  2. 使用 sgen.exe 生成序列化文件
请将 System.Xml.Serialization.XmlSerializerAssembly 属性添加到您的类中。 请用 MyClass 所在的程序集的名称替换 'MyAssembly'。
[Serializable]
[XmlSerializerAssembly("MyAssembly.XmlSerializers")]
public class MyClass
{
…
}

使用sgen.exe工具生成序列化文件,并将其与类的程序集一起部署。 “sgen.exe MyAssembly.dll”将生成文件MyAssembly.XmlSerializers.dll。 这两个更改将导致.NET直接找到程序集。 我检查过它,在.NET Framework 3.5和Visual Studio 2008上是可行的。

好的,如果没有这些更改,它失败了吗?如果是,为什么? - John Saunders
1
我找不到任何原因,解释为什么我的项目在VS2012中的4.0版本突然开始失败。 "忽略"错误不是一个选项,因为每次尝试访问Active Directory时都会出现错误; 因此忽略将意味着无法进行身份验证。 我仍然非常沮丧,因为VS2012无法正确自动生成序列化DLL。 然而,这些步骤提供了完美的解决方案。 - sfuqua

7

XmlSerializer.FromTypes函数不会抛出异常,但会泄漏内存。因此,您需要为每种类型缓存这样的序列化程序,以避免为每个创建的实例泄漏内存。

创建自己的XmlSerializer工厂并简单地使用它:

XmlSerializer serializer = XmlSerializerFactoryNoThrow.Create(typeof(MyType));

工厂外观如下:
public static class XmlSerializerFactoryNoThrow
{
    public static Dictionary<Type, XmlSerializer> _cache = new Dictionary<Type, XmlSerializer>();

    private static object SyncRootCache = new object();        

    /// <summary>
    /// //the constructor XmlSerializer.FromTypes does not throw exception, but it is said that it causes memory leaks
    /// https://dev59.com/-HNA5IYBdhLWcg3wAI3e
    /// That is why I use dictionary to cache the serializers my self.
    /// </summary>
    public static XmlSerializer Create(Type type)
    {
        XmlSerializer serializer;

        lock (SyncRootCache)
        {
            if (_cache.TryGetValue(type, out serializer))
                return serializer;
        }

        lock (type) //multiple variable of type of one type is same instance
        {
            //constructor XmlSerializer.FromTypes does not throw the first chance exception           
            serializer = XmlSerializer.FromTypes(new[] { type })[0];
            //serializer = XmlSerializerFactoryNoThrow.Create(type);
        }

        lock (SyncRootCache)
        {
            _cache[type] = serializer;
        }
        return serializer;
    }       
}

更复杂的版本,没有内存泄漏的可能(请有人审查代码):

    public static XmlSerializer Create(Type type)
    {
        XmlSerializer serializer;

        lock (SyncRootCache)
        {
            if (_cache.TryGetValue(type, out serializer))
                return serializer;
        }

        lock (type) //multiple variable of type of one type is same instance
        {
            lock (SyncRootCache)
            {
                if (_cache.TryGetValue(type, out serializer))
                    return serializer;
            }
            serializer = XmlSerializer.FromTypes(new[] { type })[0];
            lock (SyncRootCache)
            {
                _cache[type] = serializer;
            }
        }          
        return serializer;
    }       
}

你应该使用ConcurrentDictionary。这段代码可能会死锁。 - Behrooz
1
抱歉,我把词搞混了。我的意思是它可以多次插入一个项目,因为在检查存在性和插入之间有一个间隙。并发字典使用某种两阶段锁定(bag[0]然后bag[hash]])并保留必须插入/包含您正在处理的项的袋子的引用。这样更快、更安全、更干净。 - Behrooz
1
是的和不是的。你说得对,在同一时间内,可能会有两个相同类型的序列化程序在两个线程中并行创建,然后被添加到字典中两次。在这种情况下,第二次插入将只替换第一次插入,但锁定部分保证了线程安全,整体缺点是小的内存泄漏。这是性能优化,因为在实际场景中,你不希望类型A的线程一等待由类型B的线程二阻塞的序列化程序。 - Tomas Kubes
1
@Behrooz 请检查源代码的新版本。 - Tomas Kubes
1
第二个版本看起来不错。顺便说一下,你可以使用 _cache 本身进行锁定。 - Behrooz
显示剩余2条评论

6

这个异常也可以被一个名为BindingFailure的托管调试助手(MDA)捕获。

如果你的应用程序设计为与预构建的序列化程序集一起发布,那么这个MDA非常有用。我们这样做是为了提高应用程序的性能。它允许我们确保预先构建的序列化程序集通过我们的构建过程正确构建,并且在应用程序中加载时不会在运行时重新构建。

除了在这种情况下,它真的没有什么用处,因为正如其他帖子所说,当Serializer构造函数捕获绑定错误时,序列化程序集将在运行时重新构建。因此,通常可以关闭它。


3
另一方面,解决编译错误非常复杂。这些问题会表现为FileNotFoundException,并显示以下信息:
File or assembly name abcdef.dll, or one of its dependencies, was not found. File name: "abcdef.dll"
   at System.Reflection.Assembly.nLoad( ... )
   at System.Reflection.Assembly.InternalLoad( ... )
   at System.Reflection.Assembly.Load(...)
   at System.CodeDom.Compiler.CompilerResults.get_CompiledAssembly() 

您可能会想知道文件未找到异常与实例化序列化程序对象有什么关系,但请记住:构造函数编写 C# 文件并尝试编译它们。此异常的调用堆栈提供了一些好的信息来支持这种怀疑。异常发生时,XmlSerializer 试图加载由 CodeDOM 调用 System.Reflection.Assembly.Load 方法生成的程序集。该异常没有提供关于 XmlSerializer 应该创建的程序集不存在原因的解释。通常情况下,程序集不存在是因为编译失败,这可能是因为在罕见情况下,序列化属性生成的代码无法通过 C# 编译器编译而导致。 注意 当 XmlSerializer 在无法访问临时目录的帐户或安全环境下运行时,也会出现此错误。

Source: http://msdn.microsoft.com/en-us/library/aa302290.aspx


他没有指明这是在运行时发生的。我能想到的另一件事是,你可能有一个命名空间/类冲突。你的MyType的完整名称是什么? - Yvo
是的,我查看了你的链接,构造函数的信息虽然有帮助,但并不是我所需要的。 - Irwin
6
你可以在运行时编译。这就是XmlSerializer所做的。它会在运行时动态地构建一个程序集,用于对特定类型进行XML序列化和反序列化。由于某些原因,这个过程在问题提出者那里失败了。可能是由于权限问题,例如在临时目录上(可能甚至是磁盘空间不足等小问题)。 - nos
你确定吗?我非常确定序列化的东西在编译期间被编译成名为YourAssemblyName.XmlSerializers.dll的程序集,而不是在运行时编译。这可能由于各种原因而失败,至少包括部署文件夹中的NTFS权限问题。 - tomfanning
1
我希望我能够多次点赞。你提到账户无法访问临时文件夹的笔记为我触发了答案。一旦我将我的服务账户添加到服务器上的管理员组中,它就可以正常工作了。谢谢! - Bob Horn
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接