Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FileNotFoundException in ApplicationSettingsBase

When debugging an application I always get the following error when break on exception is enabled in Visual Studio. This is really bugging me, since we work with break on exception. The funny thing is, that it still works when I continue (the StringCollection is loaded).

The Message is:

Could not load file or assembly 'System.XmlSerializers, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or one of its dependencies. The system cannot find the file specified.

Here is the code that is causing the exception (designer generated)

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

I tried to create an empty test application that shows the error, but the exception didn't occur. Our project is huge so it tough to find the cause. Maybe someone on this site has a clue how to solve this.

like image 287
testalino Avatar asked Aug 16 '10 15:08

testalino


1 Answers

Just an explanation for why this exception is thrown. You can repro the exception with this sample Windows Forms app. Start by adding a setting named "Setting" of type StringCollection. Click the dots in the Value column and enter a couple of strings. Make the form class code look like this:

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, tick the Thrown checkbox for CLR exceptions. Run the form and close it, the debugger will stop when the exception is thrown. The top of the call stack looks like this:

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   

You can see the XmlSerializer class hunting for an assembly that contains the XML serializer for the StringCollection class. The LoadGeneratedAssembly method looks like this with the boring bits removed:

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)     {       ...     }   .... } 

And Compiler.GetTempAssemblyName():

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

This GetTempAssemblyName is the evil-doer in this case. The StringCollection class lives in the System.dll assembly, the method generates the name "System.XmlSerializers". This method is designed to find the assembly for your own classes, the one generated by Sgen.exe. Like WindowsApplication1.XmlSerializers.dll for your sample program. But StringCollection is a class in the .NET Framework, the assembly name it generates just isn't valid. There isn't actually a "System.XmlSerializers.dll" assembly in the framework.

Feedback reports about this behavior at connect.microsoft.com have all been closed with "By Design". It was, the original designers considered the cost of preventing the exception too high and decided to just catch the exception. Which all works fine, the exception is indeed caught. You just happen to see it because you got the Thrown checkbox turned on in the Debug + Exceptions dialog.

Making the Xml serialization code behave differently here is not an option. It would have been easy enough for them to simply filter out types in the System.dll assembly, but that's a potentially never-ending battle, there are a lot more assemblies in the framework. A workaround is to use your own class to store the setting instead of using a StringCollection.

like image 61
Hans Passant Avatar answered Sep 18 '22 14:09

Hans Passant