我一直在使用的一个应用程序在尝试序列化类型时失败了。

像这样的陈述

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)

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

我该如何解决这个问题?


当前回答

我的解决方案是直接使用反射来创建序列化器。这就绕过了导致异常的奇怪文件加载。我将其打包在一个helper函数中,该函数还负责缓存序列化器。

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

其他回答

Like Martin Sherburn said, this is normal behavior. The constructor of the XmlSerializer first tries to find an assembly named [YourAssembly].XmlSerializers.dll which should contain the generated class for serialization of your type. Since such a DLL has not been generated yet (they are not by default), a FileNotFoundException is thrown. When that happenes, XmlSerializer's constructor catches that exception, and the DLL is generated automatically at runtime by the XmlSerializer's constructor (this is done by generating C# source files in the %temp% directory of your computer, then compiling them using the C# compiler). Additional constructions of an XmlSerializer for the same type will just use the already generated DLL.

更新:从。net 4.5开始,XmlSerializer不再执行代码生成,也不再为了在运行时创建序列化程序集而使用c#编译器执行编译,除非通过设置配置文件设置(useLegacySerializerGeneration)显式强制执行。此更改消除了对csc.exe的依赖,并提高了启动性能。来源:. net Framework 4.5 Readme,章节1.3.8.1。

异常由XmlSerializer的构造函数处理。你不需要自己做任何事情,你可以点击“继续”(F5)继续执行你的程序,一切都会好的。如果你被异常停止程序执行并弹出异常助手所困扰,你要么关闭“只是我的代码”,要么将FileNotFoundException设置为在抛出时中断执行,而不是在“User-unhandled”时。

打开“Just My Code”,请转到“Tools >> Options >> Debugging >> General >> enable Just My Code”。要关闭FileNotFound被抛出时的执行中断,请转到调试>>异常>>找到>>输入'FileNotFoundException' >>取消System.IO.FileNotFoundException中的' thrown '复选框。

有一个变通的办法。如果你使用

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

它应该避免这种异常。这对我很管用。

警告:不要多次使用,否则会出现内存泄漏

如果多次使用此方法为同一类型创建XmlSerializer实例,将会导致内存严重泄漏!

这是因为该方法绕过了内置缓存提供的XmlSerializer(type)和XmlSerializer(type, defaultNameSpace)构造函数(所有其他构造函数也绕过缓存)。

如果使用任何方法来创建XmlSerializer,而不是通过这两个构造函数,那么必须实现自己的缓存,否则会导致内存泄漏。

看到过很多使用ConcurrentDictionary的建议,但没有可靠的例子,所以我将全身心投入到这个解决方案竞赛中。我不是一个线程安全的开发人员,所以如果这段代码不可靠,请为那些跟随的人说出来。

public static class XmlSerializerHelper
{
    private static readonly ConcurrentDictionary<Type, XmlSerializer> TypeSerializers = new ConcurrentDictionary<Type, XmlSerializer>();

    public static XmlSerializer GetSerializer(Type type)
    {
        return TypeSerializers.GetOrAdd(type,
        t =>
        {
            var importer = new XmlReflectionImporter();
            var mapping = importer.ImportTypeMapping(t, null, null);
            return new XmlSerializer(mapping);
        });
    }
}

我看过其他关于ConcurrentDictionary和Lazy加载值的文章。我不确定这是否与此相关,但这里是它的代码:

private static readonly ConcurrentDictionary<Type, Lazy<XmlSerializer>> TypeSerializers = new ConcurrentDictionary<Type, Lazy<XmlSerializer>>();

public static XmlSerializer GetSerializer(Type type)
{
    return TypeSerializers.GetOrAdd(type,
    t =>
    {
        var importer = new XmlReflectionImporter();
        var mapping = importer.ImportTypeMapping(t, null, null);
        var lazyResult = new Lazy<XmlSerializer>(() => new XmlSerializer(mapping), LazyThreadSafetyMode.ExecutionAndPublication);
        return lazyResult;
    }).Value;
}

在我的一个。net标准dll中也有类似的问题。

我使用了Microsoft.XmlSerializer.Generator nuget,它可以在. net Core和. net Standard上预生成XmlSerializer。

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
    /// http://stackoverflow.com/questions/1127431/xmlserializer-giving-filenotfoundexception-at-constructor
    /// 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;
    }       
}