| File: System\Runtime\Serialization\SurrogateSelector.cs | Web Access |
| Project: src\runtime\src\libraries\System.Runtime.Serialization.Formatters\src\System.Runtime.Serialization.Formatters.csproj (System.Runtime.Serialization.Formatters) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Diagnostics; namespace System.Runtime.Serialization { [Obsolete(Obsoletions.LegacyFormatterMessage, DiagnosticId = Obsoletions.LegacyFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public class SurrogateSelector : ISurrogateSelector { internal readonly SurrogateHashtable _surrogates = new SurrogateHashtable(32); internal ISurrogateSelector? _nextSelector; public virtual void AddSurrogate(Type type, StreamingContext context, ISerializationSurrogate surrogate) { ArgumentNullException.ThrowIfNull(type); ArgumentNullException.ThrowIfNull(surrogate); var key = new SurrogateKey(type, context); _surrogates.Add(key, surrogate); // Hashtable does duplicate checking. } private static bool HasCycle(ISurrogateSelector selector) { Debug.Assert(selector != null, "[HasCycle]selector!=null"); ISurrogateSelector? head = selector, tail = selector; while (head != null) { head = head.GetNextSelector(); if (head == null) { return true; } if (head == tail) { return false; } head = head.GetNextSelector(); tail = tail!.GetNextSelector(); if (head == tail) { return false; } } return true; } // Adds another selector to check if we don't have match within this selector. // The logic is:"Add this onto the list as the first thing that you check after yourself." public virtual void ChainSelector(ISurrogateSelector selector) { ArgumentNullException.ThrowIfNull(selector); // Verify that we don't try and add ourself twice. if (selector == this) { throw new SerializationException(SR.Serialization_SurrogateCycle); } // Verify that the argument doesn't contain a cycle. if (!HasCycle(selector)) { throw new ArgumentException(SR.Serialization_SurrogateCycleInArgument, nameof(selector)); } // Check for a cycle that would lead back to this. We find the end of the list that we're being asked to // insert for use later. ISurrogateSelector? tempCurr = selector.GetNextSelector(); ISurrogateSelector tempEnd = selector; while (tempCurr != null && tempCurr != this) { tempEnd = tempCurr; tempCurr = tempCurr.GetNextSelector(); } if (tempCurr == this) { throw new ArgumentException(SR.Serialization_SurrogateCycle, nameof(selector)); } // Check for a cycle later in the list which would be introduced by this insertion. tempCurr = selector; ISurrogateSelector? tempPrev = selector; while (tempCurr != null) { if (tempCurr == tempEnd) { tempCurr = GetNextSelector(); } else { tempCurr = tempCurr.GetNextSelector(); } if (tempCurr == null) { break; } if (tempCurr == tempPrev) { throw new ArgumentException(SR.Serialization_SurrogateCycle, nameof(selector)); } if (tempCurr == tempEnd) { tempCurr = GetNextSelector(); } else { tempCurr = tempCurr.GetNextSelector(); } if (tempPrev == tempEnd) { tempPrev = GetNextSelector(); } else { Debug.Assert(tempPrev != null); tempPrev = tempPrev.GetNextSelector(); } if (tempCurr == tempPrev) { throw new ArgumentException(SR.Serialization_SurrogateCycle, nameof(selector)); } } // Add the new selector and it's entire chain of selectors as the next thing that we check. ISurrogateSelector? temp = _nextSelector; _nextSelector = selector; if (temp != null) { tempEnd.ChainSelector(temp); } } // Get the next selector on the chain of selectors. public virtual ISurrogateSelector? GetNextSelector() => _nextSelector; // Gets the surrogate for a particular type. If this selector can't // provide a surrogate, it checks with all of it's children before returning null. public virtual ISerializationSurrogate? GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) { ArgumentNullException.ThrowIfNull(type); selector = this; SurrogateKey key = new SurrogateKey(type, context); ISerializationSurrogate? temp = (ISerializationSurrogate?)_surrogates[key]; if (temp != null) { return temp; } if (_nextSelector != null) { return _nextSelector.GetSurrogate(type, context, out selector); } return null; } // Removes the surrogate associated with a given type. Does not // check chained surrogates. public virtual void RemoveSurrogate(Type type, StreamingContext context) { ArgumentNullException.ThrowIfNull(type); SurrogateKey key = new SurrogateKey(type, context); _surrogates.Remove(key); } } internal sealed class SurrogateKey { internal readonly Type _type; internal readonly StreamingContext _context; internal SurrogateKey(Type type, StreamingContext context) { Debug.Assert(type != null); _type = type; _context = context; } public override int GetHashCode() => _type.GetHashCode(); } // Subclass to override KeyEquals. internal sealed class SurrogateHashtable : Hashtable { internal SurrogateHashtable(int size) : base(size) { } // Must return true if the context to serialize for (givenContext) // is a subset of the context for which the serialization selector is provided (presentContext) // Note: This is done by overriding KeyEquals rather than overriding Equals() in the SurrogateKey // class because Equals() method must be commutative. // n.b. 'key' and 'item' parameter positions swapped from base method! #pragma warning disable CS8765 // Nullability of parameter 'key' doesn't match overridden member protected override bool KeyEquals(object key, object item) #pragma warning restore CS8765 { SurrogateKey givenValue = (SurrogateKey)item; SurrogateKey presentValue = (SurrogateKey)key; return presentValue._type == givenValue._type && (presentValue._context.State & givenValue._context.State) == givenValue._context.State && presentValue._context.Context == givenValue._context.Context; } } }