File: System\Reflection\TypeLoading\General\RoAssemblyName.cs
Web Access
Project: src\src\libraries\System.Reflection.MetadataLoadContext\src\System.Reflection.MetadataLoadContext.csproj (System.Reflection.MetadataLoadContext)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Reflection.TypeLoading
{
    //
    // This is a private assembly name abstraction that's more suitable for use as keys in our caches.
    //
    //  - Immutable, unlike the public AssemblyName
    //  - Has a useful Equals() override, unlike the public AssemblyName.
    //  - Implements IEquatable<> so dictionaries avoid the casting Equals()
    //  - Restricts itself to being an assembly name. Not an assembly name plus all kind of random info that someone
    //    found convenient to thumbtack onto it.
    //
    // We use this as our internal interchange type and only convert to and from the public AssemblyName class at api boundaries.
    //
 
    internal sealed class RoAssemblyName : IEquatable<RoAssemblyName>
    {
        public string Name { get; }
        public Version Version { get; }
        public string CultureName { get; }
        public byte[] PublicKeyToken;
 
        // We store the flags to support "Retargetable".
        // The only flag allowed in an ECMA-335 AssemblyReference is the "PublicKey" bit. Since
        // RoAssemblyName always normalizes to the short form public key token, that bit would always be 0.
        public AssemblyNameFlags Flags { get; }
 
        private static readonly Version s_Version0000 = new Version(0, 0, 0, 0);
 
        public RoAssemblyName(string? name, Version? version, string? cultureName, byte[]? publicKeyToken, AssemblyNameFlags flags)
        {
            // We forcefully normalize the representation so that Equality is dependable and fast.
            Debug.Assert(name != null);
 
            Name = name;
            Version = version ?? s_Version0000;
            CultureName = cultureName ?? string.Empty;
            PublicKeyToken = publicKeyToken ?? Array.Empty<byte>();
            Flags = flags;
        }
 
        public string FullName => ToAssemblyName().FullName;
 
        // Equality - this compares every bit of data in the RuntimeAssemblyName which is acceptable for use as keys in a cache
        // where semantic duplication is permissible. This method is *not* meant to define ref->def binding rules or
        // assembly binding unification rules.
        public bool Equals(RoAssemblyName? other)
        {
            Debug.Assert(other is not null);
            if (Name != other.Name)
                return false;
            if (Version != other.Version)
                return false;
            if (CultureName != other.CultureName)
                return false;
            if (!(((ReadOnlySpan<byte>)PublicKeyToken).SequenceEqual(other.PublicKeyToken)))
                return false;
 
            // Do not compare Flags; we do not want to treat AssemblyNames as not being equal due to Flags.
 
            return true;
        }
 
        public sealed override bool Equals([NotNullWhen(true)] object? obj) => obj is RoAssemblyName other && Equals(other);
        public sealed override int GetHashCode() => Name.GetHashCode();
        public sealed override string ToString() => FullName;
 
        public AssemblyName ToAssemblyName()
        {
            AssemblyName an = new AssemblyName()
            {
                Name = Name,
                Version = Version,
                CultureName = CultureName,
                Flags = Flags,
            };
 
            // We must not hand out our own copy of the PKT to AssemblyName as AssemblyName is amazingly trusting and gives untrusted callers
            // full freedom to scribble on its PKT array. (As do we but we only have trusted callers!)
            an.SetPublicKeyToken(PublicKeyToken.CloneArray());
            return an;
        }
    }
}