Serializacja w programie obsługi OnAssemblyResolve może powodować rekursję

Ten artykuł pomaga rozwiązać problem polegający na tym, że serializacja spowoduje, że program obsługi rozpoznawania zestawów będzie wywoływany rekursywnie.

Oryginalna wersja produktu: .NET Framework 3.5 z dodatkiem Service Pack 1, 4.5
Oryginalny numer KB: 2756498

Objawy

Załóżmy, że masz klasę oznaczoną za pomocą atrybutu XmlSerializerAssembly , jak w poniższym przykładzie:

[Serializable, XmlRoot(ElementName="Bindings", IsNullable=false), XmlSerializerAssembly(AssemblyName="Contoso.ObjectLoaders.Bindings")]
public class Bindings
{
    (...)
}

Wygenerowany zestaw jest podpisany i umieszczany w globalnej pamięci podręcznej zestawów (GAC) zamiast w folderze bin . Zaimplementujesz również procedurę obsługi dla AppDomain.AssemblyResolve zdarzeń i serializujesz wystąpienie klasy w metodzie procedury obsługi rozpoznawania zestawów w następujący sposób:

private static Assembly ResolveAssemblies(object sender, ResolveEventArgs args)
{
    // Some code here
    var serializer = new XmlSerializer(typeof(Bindings));
    // Some more code here
}

W tym scenariuszu serializacja spowoduje, że program obsługi rozpoznawania zestawów będzie wywoływany rekursywnie, co może spowodować przepełnienie stosu.

Przyczyna

Jest to zamierzone działanie. Zestawy z atrybutem XmlSerializerAssembly są ładowane z elementem Assembly.LoadWithPartialName(string), co spowoduje ponowne wywołanie OnResolveAssembly procedury obsługi.

Obejście 1. Usuwanie atrybutu XmlSerializerAssembly

Ta metoda uniemożliwi załadowanie zestawu przez Assembly.LoadWithPartialName(string)element .

Obejście 2. Pisanie bardziej niezawodnej procedury obsługi rozpoznawania zestawów

Jest to zalecane obejście i rozwiąże wszelkie problemy związane z rekursją. Poniższy przykład to prosty szablon kodu dla tego obejścia. Głównym pomysłem jest dodanie już rozwiązanych zestawów do listy ogólnej (współbieżna torba została wybrana, ponieważ jej wątkowy bezpieczny) i powrócić, jeśli zestaw jest już w trakcie rozwiązywania problemu.

static ConcurrentBag<string> listOfAssemblies = new ConcurrentBag<string>();
private static Assembly ResolveAssemblies(object sender, ResolveEventArgs args)
{
    if (listOfAssemblies.Contains(args.Name))
    {
        // Already resolving this assembly, return now
        return null;
    }
    try
    {
        listOfAssemblies.Add(args.Name);
        // Add your handler code here
    }
    finally
    {
        // Assembly was handled, remove from list
        listOfAssemblies.Remove(args.Name);
    }
}

Źródła