File: src\libraries\System.Private.CoreLib\src\System\Runtime\Serialization\SerializationInfo.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// 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.Generic;
using System.Diagnostics;
 
#pragma warning disable SYSLIB0050 // The SerializationInfo type is part of the obsolete legacy serialization framework
 
namespace System.Runtime.Serialization
{
    /// <summary>The structure for holding all of the data needed for object serialization and deserialization.</summary>
    public sealed partial class SerializationInfo
    {
        private const int DefaultSize = 4;
 
        // Even though we have a dictionary, we're still keeping all the arrays around for back-compat.
        // Otherwise we may run into potentially breaking behaviors like GetEnumerator() not returning entries in the same order they were added.
        private string[] _names;
        private object?[] _values;
        private Type[] _types;
        private int _count;
        private readonly Dictionary<string, int> _nameToIndex;
        private readonly IFormatterConverter _converter;
        private string _rootTypeName;
        private string _rootTypeAssemblyName;
        private Type _rootType;
 
        [CLSCompliant(false)]
        [Obsolete(Obsoletions.LegacyFormatterMessage, DiagnosticId = Obsoletions.LegacyFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public SerializationInfo(Type type, IFormatterConverter converter)
        {
            ArgumentNullException.ThrowIfNull(type);
            ArgumentNullException.ThrowIfNull(converter);
 
            _rootType = type;
            _rootTypeName = type.FullName!;
            _rootTypeAssemblyName = type.Module.Assembly.FullName!;
 
            _names = new string[DefaultSize];
            _values = new object[DefaultSize];
            _types = new Type[DefaultSize];
 
            _nameToIndex = new Dictionary<string, int>();
 
            _converter = converter;
        }
 
        [CLSCompliant(false)]
        [Obsolete(Obsoletions.LegacyFormatterMessage, DiagnosticId = Obsoletions.LegacyFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public SerializationInfo(Type type, IFormatterConverter converter, bool requireSameTokenInPartialTrust)
            : this(type, converter)
        {
            // requireSameTokenInPartialTrust is a vacuous parameter in a platform that does not support partial trust.
        }
 
        public string FullTypeName
        {
            get => _rootTypeName;
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                _rootTypeName = value;
                IsFullTypeNameSetExplicit = true;
            }
        }
 
        public string AssemblyName
        {
            get => _rootTypeAssemblyName;
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                _rootTypeAssemblyName = value;
                IsAssemblyNameSetExplicit = true;
            }
        }
 
        public bool IsFullTypeNameSetExplicit { get; private set; }
 
        public bool IsAssemblyNameSetExplicit { get; private set; }
 
        public void SetType(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            if (!ReferenceEquals(_rootType, type))
            {
                _rootType = type;
                _rootTypeName = type.FullName!;
                _rootTypeAssemblyName = type.Module.Assembly.FullName!;
                IsFullTypeNameSetExplicit = false;
                IsAssemblyNameSetExplicit = false;
            }
        }
 
        public int MemberCount => _count;
 
        public Type ObjectType => _rootType;
 
        public SerializationInfoEnumerator GetEnumerator() => new SerializationInfoEnumerator(_names, _values, _types, _count);
 
        private void ExpandArrays()
        {
            int newSize;
            Debug.Assert(_names.Length == _count, "[SerializationInfo.ExpandArrays]_names.Length == _count");
 
            newSize = (_count * 2);
 
            // In the pathological case, we may wrap
            if (newSize < _count)
            {
                if (int.MaxValue > _count)
                {
                    newSize = int.MaxValue;
                }
            }
 
            // Allocate more space and copy the data
            string[] newMembers = new string[newSize];
            object[] newData = new object[newSize];
            Type[] newTypes = new Type[newSize];
 
            Array.Copy(_names, newMembers, _count);
            Array.Copy(_values, newData, _count);
            Array.Copy(_types, newTypes, _count);
 
            // Assign the new arrays back to the member vars.
            _names = newMembers;
            _values = newData;
            _types = newTypes;
        }
 
        public void AddValue(string name, object? value, Type type)
        {
            ArgumentNullException.ThrowIfNull(name);
            ArgumentNullException.ThrowIfNull(type);
 
            AddValueInternal(name, value, type);
        }
 
        public void AddValue(string name, object? value)
        {
            if (null == value)
            {
                AddValue(name, value, typeof(object));
            }
            else
            {
                AddValue(name, value, value.GetType());
            }
        }
 
        public void AddValue(string name, bool value)
        {
            AddValue(name, (object)value, typeof(bool));
        }
 
        public void AddValue(string name, char value)
        {
            AddValue(name, (object)value, typeof(char));
        }
 
        [CLSCompliant(false)]
        public void AddValue(string name, sbyte value)
        {
            AddValue(name, (object)value, typeof(sbyte));
        }
 
        public void AddValue(string name, byte value)
        {
            AddValue(name, (object)value, typeof(byte));
        }
 
        public void AddValue(string name, short value)
        {
            AddValue(name, (object)value, typeof(short));
        }
 
        [CLSCompliant(false)]
        public void AddValue(string name, ushort value)
        {
            AddValue(name, (object)value, typeof(ushort));
        }
 
        public void AddValue(string name, int value)
        {
            AddValue(name, (object)value, typeof(int));
        }
 
        [CLSCompliant(false)]
        public void AddValue(string name, uint value)
        {
            AddValue(name, (object)value, typeof(uint));
        }
 
        public void AddValue(string name, long value)
        {
            AddValue(name, (object)value, typeof(long));
        }
 
        [CLSCompliant(false)]
        public void AddValue(string name, ulong value)
        {
            AddValue(name, (object)value, typeof(ulong));
        }
 
        public void AddValue(string name, float value)
        {
            AddValue(name, (object)value, typeof(float));
        }
 
        public void AddValue(string name, double value)
        {
            AddValue(name, (object)value, typeof(double));
        }
 
        public void AddValue(string name, decimal value)
        {
            AddValue(name, (object)value, typeof(decimal));
        }
 
        public void AddValue(string name, DateTime value)
        {
            AddValue(name, (object)value, typeof(DateTime));
        }
 
        internal void AddValueInternal(string name, object? value, Type type)
        {
            if (!_nameToIndex.TryAdd(name, _count))
            {
                throw new SerializationException(SR.Serialization_SameNameTwice);
            }
 
            // If we need to expand the arrays, do so.
            if (_count >= _names.Length)
            {
                ExpandArrays();
            }
 
            // Add the data and then advance the counter.
            _names[_count] = name;
            _values[_count] = value;
            _types[_count] = type;
            _count++;
        }
 
        /// <summary>
        /// Finds the value if it exists in the current data. If it does, we replace
        /// the values, if not, we append it to the end. This is useful to the
        /// ObjectManager when it's performing fixups.
        ///
        /// All error checking is done with asserts. Although public in coreclr,
        /// it's not exposed in a contract and is only meant to be used by other runtime libraries.
        ///
        /// This isn't a public API, but it gets invoked dynamically by
        /// BinaryFormatter
        ///
        /// This should not be used by clients: exposing out this functionality would allow children
        /// to overwrite their parent's values. It is public in order to give other runtime libraries access to it for
        /// its ObjectManager implementation, but it should not be exposed out of a contract.
        /// </summary>
        /// <param name="name"> The name of the data to be updated.</param>
        /// <param name="value"> The new value.</param>
        /// <param name="type"> The type of the data being added.</param>
        internal void UpdateValue(string name, object value, Type type)
        {
            Debug.Assert(null != name, "[SerializationInfo.UpdateValue]name!=null");
            Debug.Assert(null != value, "[SerializationInfo.UpdateValue]value!=null");
            Debug.Assert(type is not null, "[SerializationInfo.UpdateValue]type!=null");
 
            int index = FindElement(name);
            if (index < 0)
            {
                AddValueInternal(name, value, type);
            }
            else
            {
                _values[index] = value;
                _types[index] = type;
            }
        }
 
        private int FindElement(string name)
        {
            ArgumentNullException.ThrowIfNull(name);
 
            if (_nameToIndex.TryGetValue(name, out int index))
            {
                return index;
            }
            return -1;
        }
 
        /// <summary>
        /// Gets the location of a particular member and then returns
        /// the value of the element at that location.  The type of the member is
        /// returned in the foundType field.
        /// </summary>
        /// <param name="name"> The name of the element to find.</param>
        /// <param name="foundType"> The type of the element associated with the given name.</param>
        /// <returns>The value of the element at the position associated with name.</returns>
        private object? GetElement(string name, out Type foundType)
        {
            int index = FindElement(name);
            if (index == -1)
            {
                throw new SerializationException(SR.Format(SR.Serialization_NotFound, name));
            }
 
            Debug.Assert(index < _values.Length, "[SerializationInfo.GetElement]index<_values.Length");
            Debug.Assert(index < _types.Length, "[SerializationInfo.GetElement]index<_types.Length");
 
            foundType = _types[index];
            Debug.Assert(foundType is not null, "[SerializationInfo.GetElement]foundType!=null");
            return _values[index];
        }
 
        private object? GetElementNoThrow(string name, out Type? foundType)
        {
            int index = FindElement(name);
            if (index == -1)
            {
                foundType = null;
                return null;
            }
 
            Debug.Assert(index < _values.Length, "[SerializationInfo.GetElement]index<_values.Length");
            Debug.Assert(index < _types.Length, "[SerializationInfo.GetElement]index<_types.Length");
 
            foundType = _types[index];
            Debug.Assert(foundType is not null, "[SerializationInfo.GetElement]foundType!=null");
            return _values[index];
        }
 
        public object? GetValue(string name, Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            if (type is not RuntimeType)
                throw new ArgumentException(SR.Argument_MustBeRuntimeType);
 
            object? value = GetElement(name, out Type foundType);
 
            if (ReferenceEquals(foundType, type) || type.IsAssignableFrom(foundType) || value == null)
            {
                return value;
            }
 
            Debug.Assert(_converter != null, "[SerializationInfo.GetValue]_converter!=null");
            return _converter.Convert(value, type);
        }
 
        internal object? GetValueNoThrow(string name, Type type)
        {
            Debug.Assert(type is not null, "[SerializationInfo.GetValue]type ==null");
            Debug.Assert(type is RuntimeType, "[SerializationInfo.GetValue]type is not a runtime type");
 
            object? value = GetElementNoThrow(name, out Type? foundType);
            if (value == null)
                return null;
 
            if (ReferenceEquals(foundType, type) || type.IsAssignableFrom(foundType))
            {
                return value;
            }
 
            Debug.Assert(_converter != null, "[SerializationInfo.GetValue]_converter!=null");
 
            return _converter.Convert(value, type);
        }
 
        public bool GetBoolean(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(bool)) ? (bool)value! : _converter.ToBoolean(value!); // if value is null To* method will either deal with it or throw
        }
 
        public char GetChar(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(char)) ? (char)value! : _converter.ToChar(value!);
        }
 
        [CLSCompliant(false)]
        public sbyte GetSByte(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(sbyte)) ? (sbyte)value! : _converter.ToSByte(value!);
        }
 
        public byte GetByte(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(byte)) ? (byte)value! : _converter.ToByte(value!);
        }
 
        public short GetInt16(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(short)) ? (short)value! : _converter.ToInt16(value!);
        }
 
        [CLSCompliant(false)]
        public ushort GetUInt16(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(ushort)) ? (ushort)value! : _converter.ToUInt16(value!);
        }
 
        public int GetInt32(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(int)) ? (int)value! : _converter.ToInt32(value!);
        }
 
        [CLSCompliant(false)]
        public uint GetUInt32(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(uint)) ? (uint)value! : _converter.ToUInt32(value!);
        }
 
        public long GetInt64(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(long)) ? (long)value! : _converter.ToInt64(value!);
        }
 
        [CLSCompliant(false)]
        public ulong GetUInt64(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(ulong)) ? (ulong)value! : _converter.ToUInt64(value!);
        }
 
        public float GetSingle(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(float)) ? (float)value! : _converter.ToSingle(value!);
        }
 
 
        public double GetDouble(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(double)) ? (double)value! : _converter.ToDouble(value!);
        }
 
        public decimal GetDecimal(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(decimal)) ? (decimal)value! : _converter.ToDecimal(value!);
        }
 
        public DateTime GetDateTime(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(DateTime)) ? (DateTime)value! : _converter.ToDateTime(value!);
        }
 
        public string? GetString(string name)
        {
            object? value = GetElement(name, out Type foundType);
            return ReferenceEquals(foundType, typeof(string)) || value == null ? (string?)value : _converter.ToString(value);
        }
    }
}