我如何在 .NET 中表示 URN(统一资源名称)以便 Equals 按预期工作
How do I represent URNs (Uniform Resource Names) in .NET so that Equals works as expected
RFC2141 提及:
Examples of lexical equivalence
The following URN comparisons highlight the lexical equivalence
definitions:
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, and 3 are all lexically equivalent.
随后的 RFC8141 保留了该等价性:
2.1. Namespace Identifier (NID)
NIDs are case insensitive (e.g., "ISBN" and "isbn" are equivalent).
我可以在 .NET 框架中轻松找到的最接近 URN 的表示是 URI class。但是,它似乎并没有完全遵守 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 class 遵守等价定义吗?我应该使用其他 class 吗?
请注意,我找到了 URN class,但文档中提到不应直接使用它。
Uri class does not support a specific parser for the urn:
scheme out of the box. Maybe understandably so, because even if the comparison rules for the NID specify that it is case-insensitive, the rules for comparing two NSS would depend on rules as defined by the particular namespace, per RFC 8141.
对于快速而肮脏的方法,您可以尝试使用 Uri.Compare() 方法。对于两个 URI 相同的情况,它将 return 为零,否则为非零。
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);
}
}
}
RFC2141 提及:
Examples of lexical equivalence
The following URN comparisons highlight the lexical equivalence
definitions: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, and 3 are all lexically equivalent.
随后的 RFC8141 保留了该等价性:
2.1. Namespace Identifier (NID)
NIDs are case insensitive (e.g., "ISBN" and "isbn" are equivalent).
我可以在 .NET 框架中轻松找到的最接近 URN 的表示是 URI class。但是,它似乎并没有完全遵守 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 class 遵守等价定义吗?我应该使用其他 class 吗?
请注意,我找到了 URN class,但文档中提到不应直接使用它。
Uri class does not support a specific parser for the urn:
scheme out of the box. Maybe understandably so, because even if the comparison rules for the NID specify that it is case-insensitive, the rules for comparing two NSS would depend on rules as defined by the particular namespace, per RFC 8141.
对于快速而肮脏的方法,您可以尝试使用 Uri.Compare() 方法。对于两个 URI 相同的情况,它将 return 为零,否则为非零。
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);
}
}
}