Как мне представить URN (унифицированные имена ресурсов) в.NET, чтобы Equals работал как положено

RFC2141 упоминает:

  1. Примеры лексической эквивалентности

    Следующие сравнения URN подчеркивают лексическую эквивалентность
    определения:

       1- URN:foo:a123,456
       2- urn:foo:a123,456
       3- urn:FOO:a123,456
       4- urn:foo:A123,456
       5- urn:foo:a123%2C456
       6- URN:FOO:a123%2c456
    

    URNs 1, 2 и 3 все лексически эквивалентны.

Последующий RFC8141 сохраняет эту эквивалентность:

2.1. Идентификатор пространства имен (NID)

NID нечувствительны к регистру (например, "ISBN" и "isbn" эквивалентны).

Наиболее близким представлением для URN, которое я легко мог найти в.NET Framework, является класс URI. Тем не менее, он, похоже, не полностью соблюдает определение эквивалентности в RFC:

    [TestMethod]
    public void TestEquivalentUrnsAreBroken()
    {
        Assert.AreEqual(
            new Uri("URN:foo:a123,456"),
            new Uri("urn:foo:a123,456"));

        Assert.AreEqual(
            new Uri("urn:foo:a123,456"),
            new Uri("urn:FOO:a123,456"));
    }

В приведенном выше примере кода первое утверждение работает, как и ожидалось, тогда как второе утверждение не выполняется.

Есть ли какой-нибудь разумный способ заставить класс URI соблюдать определение эквивалентности? Есть ли другой класс, который я должен использовать вместо этого?

Обратите внимание, что я нашел класс URN, но в документации упоминается, что он не должен использоваться напрямую.

0 ответов

Класс Uri не поддерживает конкретный парсер дляurn:схема из коробки. Возможно, это и понятно, потому что даже если в правилах сравнения для NID указано, что он нечувствителен к регистру, правила сравнения двух NSS будут зависеть от правил, определенных конкретным пространством имен согласно RFC 8141.

Для быстрого и грязного подхода вы можете попробовать использовать метод Uri.Compare(). Он вернет ноль в случаях, когда оба URI эквивалентны, и ненулевое значение в противном случае.

var u1 = new Uri("URN:foo:a123,456");
var u2 = new Uri("urn:foo:a123,456");
var u3 = new Uri("urn:FOO:a123,456");
var u4 = new Uri("urn:nope:a123,456");

Console.WriteLine(Uri.Compare(u1, u2, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // 0
Console.WriteLine(Uri.Compare(u1, u3, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // 0
Console.WriteLine(Uri.Compare(u2, u3, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // 0
Console.WriteLine(Uri.Compare(u3, u4, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase)); // -8

Для более авантюрного подхода вы можете сделать что-то вроде следующего. Это потребует тщательного обдумывания для правильной реализации. Этот код не предназначен для использования как есть, а скорее как отправная точка.

using System;
using System.Text.RegularExpressions;

public class Program
{
    public static void Main()
    {
        var u1 = new Urn("URN:foo:a123,456");
        var u2 = new Urn("urn:foo:a123,456");
        var u3 = new Urn("urn:foo:a123,456");
        var u4 = new Urn("urn:FOO:a123,456");
        var u5 = new Urn("urn:not-this-one:a123,456");
        Console.WriteLine(u1 == u2); // True
        Console.WriteLine(u3 == u4); // True
        Console.WriteLine(u4 == u5); // False
    }

    public class Urn : Uri
    {
        public const string UrnScheme = "urn";
        private const RegexOptions UrnRegexOptions = RegexOptions.Singleline | RegexOptions.CultureInvariant;
        private static Regex UrnRegex = new Regex("^urn:(?<NID>[a-z|A-Z][a-z|A-Z|-]{0,30}[a-z|A-Z]):(?<NSS>.*)$", UrnRegexOptions);

        public string NID { get; }
        public string NSS { get; }

        public Urn(string s) : base(s, UriKind.Absolute)
        {
            if (this.Scheme != UrnScheme) throw new FormatException($"URN scheme must be '{UrnScheme}'.");
            var match = UrnRegex.Match(this.AbsoluteUri);
            if (!match.Success) throw new FormatException("URN's NID is invalid.");
            NID = match.Groups["NID"].Value;
            NSS = match.Groups["NSS"].Value;
        }

        public override bool Equals(object other)
        {
            if (ReferenceEquals(other, this)) return true;
            return
                other is Urn u &&
                string.Equals(NID, u.NID, StringComparison.InvariantCultureIgnoreCase) &&
                string.Equals(NSS, u.NSS, StringComparison.Ordinal);
        }

        public override int GetHashCode() => base.GetHashCode();

        public static bool operator == (Urn u1, Urn u2)
        {
            if (ReferenceEquals(u1, u2)) return true;
            if (ReferenceEquals(u1, null) || ReferenceEquals(u2, null)) return false;
            return u1.Equals(u2);
        }

        public static bool operator != (Urn u1, Urn u2)
        {
            if (ReferenceEquals(u1, u2)) return false;
            if (ReferenceEquals(u1, null) || ReferenceEquals(u2, null)) return true;
            return !u1.Equals(u2);
        }
    }
}
Другие вопросы по тегам