File: System\Formats\Nrbf\ClassInfo.cs
Web Access
Project: src\src\libraries\System.Formats.Nrbf\src\System.Formats.Nrbf.csproj (System.Formats.Nrbf)
// 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;
using System.IO;
using System.Reflection.Metadata;
using System.Formats.Nrbf.Utils;
using System.Runtime.Serialization;
 
namespace System.Formats.Nrbf;
 
/// <summary>
/// Class info that provides type and member names.
/// </summary>
/// <remarks>
/// ClassInfo structures are described in <see href="https://learn.microsoft.com/openspecs/windows_protocols/ms-nrbf/0a192be0-58a1-41d0-8a54-9c91db0ab7bf">[MS-NRBF] 2.3.1.1</see>.
/// </remarks>
[DebuggerDisplay("{TypeName}")]
internal sealed class ClassInfo
{
    private readonly string _rawName;
    private TypeName? _typeName;
 
    private ClassInfo(SerializationRecordId id, string rawName, Dictionary<string, int> memberNames)
    {
        Id = id;
        _rawName = rawName;
        MemberNames = memberNames;
    }
 
    internal SerializationRecordId Id { get; }
 
    internal TypeName TypeName
    {
        get
        {
            Debug.Assert(_typeName is not null);
            return _typeName;
        }
    }
 
    internal Dictionary<string, int> MemberNames { get; }
 
    internal static ClassInfo Decode(BinaryReader reader)
    {
        SerializationRecordId id = SerializationRecordId.Decode(reader);
        string typeName = reader.ReadString();
        int memberCount = reader.ReadInt32();
 
        // Use Dictionary instead of List so that searching for member IDs by name
        // is O(n) instead of O(m * n), where m = memberCount and n = memberNameLength,
        // in degenerate cases. Since memberCount may be hostile, don't allow it to be
        // used as the initial capacity in the collection instance.
        Dictionary<string, int> memberNames = new(StringComparer.Ordinal);
        for (int i = 0; i < memberCount; i++)
        {
            // The NRBF specification does not prohibit multiple members with the same names,
            // however it's impossible to get such output with BinaryFormatter,
            // so we prohibit that on purpose.
            string memberName = reader.ReadString();
#if NET
            if (memberNames.TryAdd(memberName, i))
            {
                continue;
            }
#else
            if (!memberNames.ContainsKey(memberName))
            {
                memberNames.Add(memberName, i);
                continue;
            }
#endif
            ThrowHelper.ThrowDuplicateMemberName();
        }
 
        return new ClassInfo(id, typeName, memberNames);
    }
 
    internal void LoadTypeName(BinaryLibraryRecord libraryRecord, PayloadOptions payloadOptions)
        => _typeName = _rawName.ParseNonSystemClassRecordTypeName(libraryRecord, payloadOptions);
 
    internal void LoadTypeName(PayloadOptions payloadOptions)
        => _typeName = _rawName.ParseSystemRecordTypeName(payloadOptions);
}