应用程序设置基类中的FileNotFoundException

36
当我在Visual Studio中启用“异常断点”时,调试应用程序时总是会遇到以下错误。这真的很烦人,因为我们通常使用异常断点。有趣的是,当我继续运行代码后(StringCollection已加载),它仍然能正常工作。
错误信息如下:
“无法加载文件或程序集'System.XmlSerializers, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'或其依赖项之一。系统找不到指定的文件。”
以下是导致异常的代码(由设计器生成):
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.Specialized.StringCollection Mru {
        get {
            return ((global::System.Collections.Specialized.StringCollection)(this["Mru"]));
        }
        set {
            this["Mru"] = value;
        }
    }

我尝试创建一个空的测试应用程序以显示错误,但异常没有发生。我们的项目非常庞大,难以找到原因。也许这个网站上的某个人有解决方法的线索。

3个回答

63

这个异常被抛出的原因是什么,这里给出一个解释。您可以使用这个简单的Windows Forms应用程序来重现此异常。首先添加一个名为“Setting”的StringCollection类型的设置。点击“值”列中的点并输入一些字符串。让窗体类代码看起来像这样:

public partial class Form1 : Form {
    public Form1() {
        InitializeComponent();
    }
    protected override void OnFormClosing(FormClosingEventArgs e) {
        Properties.Settings.Default.Setting[0] = DateTime.Now.ToString();
        Properties.Settings.Default.Save();
        base.OnFormClosing(e);
    }
}

在Debug + Exceptions中,勾选CLR exceptions的Thrown复选框。运行表单并关闭它,当异常被抛出时,调试器将停止。调用栈顶部如下所示:

mscorlib.dll!System.Reflection.Assembly.nLoad(System.Reflection.AssemblyName fileName, string codeBase, System.Security.Policy.Evidence assemblySecurity, System.Reflection.Assembly locationHint, ref System.Threading.StackCrawlMark stackMark, bool throwOnFileNotFound, bool forIntrospection) + 0x2c bytes 
mscorlib.dll!System.Reflection.Assembly.InternalLoad(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, ref System.Threading.StackCrawlMark stackMark, bool forIntrospection) + 0x80 bytes   
mscorlib.dll!System.Reflection.Assembly.Load(System.Reflection.AssemblyName assemblyRef) + 0x1d bytes   
System.Xml.dll!System.Xml.Serialization.TempAssembly.LoadGeneratedAssembly(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null, out System.Xml.Serialization.XmlSerializerImplementation contract = null) + 0xcd bytes  
System.Xml.dll!System.Xml.Serialization.XmlSerializer.XmlSerializer(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null) + 0x105 bytes  

你可以看到XmlSerializer类正在寻找一个包含StringCollection类的XML序列化器的程序集。LoadGeneratedAssembly方法如下所示,已删除了无趣的部分:

internal static Assembly LoadGeneratedAssembly(Type type, string defaultNamespace, out XmlSerializerImplementation contract)
{
    ...
    AssemblyName parent = GetName(type.Assembly, true);
    partialName = Compiler.GetTempAssemblyName(parent, defaultNamespace);
    parent.Name = partialName;
    parent.CodeBase = null;
    parent.CultureInfo = CultureInfo.InvariantCulture;
    try
    {
        serializer = Assembly.Load(parent);      // <=== here
    }
    catch (Exception exception)
    {
      ...
    }
  ....
}

Compiler.GetTempAssemblyName()是什么意思:

internal static string GetTempAssemblyName(AssemblyName parent, string ns)
{
    return (parent.Name + ".XmlSerializers" + (((ns == null) || (ns.Length == 0)) ? "" : ("." + ns.GetHashCode())));
}

在这种情况下,GetTempAssemblyName方法是罪魁祸首。StringCollection类位于System.dll程序集中,该方法生成名称“System.XmlSerializers”。该方法旨在查找自己类的程序集,即由Sgen.exe生成的程序集,例如您的示例程序的WindowsApplication1.XmlSerializers.dll。但是,StringCollection是.NET Framework中的一个类,它生成的程序集名称并不有效。实际上,在框架中不存在“System.XmlSerializers.dll”程序集。

关于此行为的反馈报告都已被关闭,原因是设计人员认为防止异常的成本太高,决定仅捕获异常。这完全可行,异常确实被捕获了。您之所以看到它,是因为您在“调试+异常”对话框中打开了Thrown复选框。

让Xml序列化代码在此处表现不同不是一个选项。他们本可以轻松地过滤掉System.dll程序集中的类型,但那是一个潜在的永无止境的战斗,在框架中有更多的程序集。解决方法是使用您自己的类来存储设置,而不是使用StringCollection。


感谢您的详细解释。我将尽量避免在未来使用StringCollection。很遗憾这是“按设计要求”的。 - testalino
所以简短的答案是在“调试”>“异常”中,取消“抛出”选项的勾选? - ChatGPT

6

由于这似乎是正常操作的一部分(参见: XmlSerializer giving FileNotFoundException at constructor),我只能提供两个解决方法:

禁用此特定异常:转到“调试/异常”,单击添加,类型:C++异常,名称:EEFileLoadException(如果您看到的是此异常),取消选中此异常的“抛出”复选框。

将设置的类型更改为字符串,并像这样访问它:

var mru = Settings.Default.Mru.Split('|');
Settings.Default.Mru = string.Join("|", mru.ToArray());

1

您捕获了太多异常,System.XmlSerializer 作为其正常操作的一部分将始终引发此异常,它由该类自身捕获并处理。请更改您的调试选项,仅捕获您自己引发的异常,而不是在 .net framework 类中被捕获和处理的异常。


1
+1,没错。我对 System.Xml 有相当多的厌恶,它不是其中最好的部分之一。 - Hans Passant
就像我所说的那样,在测试应用程序中,使用相同的设置属性不会发生异常。因此,XmlSerializer并不总是会抛出此异常。 - testalino
也许并不是每次都会出现,我还没有彻底测试过每种可能的情况。但这不是重点,重点是这是类操作的正常部分。如果你根本不进行调试,或者没有捕获代码抛出的异常,那么你就看不到它。这不是错误,而是类的工作方式。 - Ben Robinson

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