Replacing Uri with a more convenient class












4














I needed some data structure for holding Uris so I thought I try the Uri class. It quickly turned out that it's very impractical because:




  • it's missing an implicit cast from/to string

  • it doesn't automatically recognize uris as absolute or relative without specifying the UriKind

  • it doesn't parse the query-string so it cannot compare uris reliably when key-value pairs are in different order

  • it's not easy to combine an absolute uri with a relative one


Working with it is just too inconvenient. As a solution I created my own SimpleUri that should solve these issues. It doesn't support everything yet because I don't need it right now but adding a couple of more regexes later should not be a problem.



public class SimpleUri : IEquatable<SimpleUri>, IEquatable<string>
{
// https://regex101.com/r/sd288W/1
// using 'new' for _nicer_ syntax
private static readonly string UriPattern = string.Join(string.Empty, new
{
/* language=regexp */ @"^(?:(?<scheme>w+):)?",
/* language=regexp */ @"(?://(?<authority>w+))?",
/* language=regexp */ @"(?<path>[a-z0-9/:]+)",
/* language=regexp */ @"(?:?(?<query>[a-z0-9=&]+))?",
/* language=regexp */ @"(?:#(?<fragment>[a-z0-9]+))?"
});

public static readonly IEqualityComparer<SimpleUri> Comparer = EqualityComparerFactory<SimpleUri>.Create
(
equals: (x, y) => StringComparer.OrdinalIgnoreCase.Equals(x, y),
getHashCode: (obj) => StringComparer.OrdinalIgnoreCase.GetHashCode(obj)
);

public SimpleUri([NotNull] string uri)
{
if (uri == null) throw new ArgumentNullException(nameof(uri));

var uriMatch = Regex.Match
(
uri,
UriPattern,
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
);

if (!uriMatch.Success)
{
throw new ArgumentException(paramName: nameof(uri), message: $"'{uri}' is not a valid Uri.");
}

Scheme = uriMatch.Groups["scheme"];
Authority = uriMatch.Groups["authority"];
Path = uriMatch.Groups["path"];
Query =
uriMatch.Groups["query"].Success
? Regex
.Matches
(
uriMatch.Groups["query"].Value,
@"(?:^|&)(?<key>[a-z0-9]+)(?:=(?<value>[a-z0-9]+))?",
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
)
.Cast<Match>()
.ToImmutableDictionary
(
m => (ImplicitString)m.Groups["key"],
m => (ImplicitString)m.Groups["value"]
)
: ImmutableDictionary<ImplicitString, ImplicitString>.Empty;
Fragment = uriMatch.Groups["fragment"];
}

public SimpleUri(SimpleUri absoluteUri, SimpleUri relativeUri)
{
if (absoluteUri.IsRelative) throw new ArgumentException($"{nameof(absoluteUri)} must be an absolute Uri.");
if (!relativeUri.IsRelative) throw new ArgumentException($"{nameof(relativeUri)} must be a relative Uri.");

Scheme = absoluteUri.Scheme;
Authority = absoluteUri.Authority;
Path = absoluteUri.Path.Value.TrimEnd('/') + "/" + relativeUri.Path.Value.TrimStart('/');
Query = absoluteUri.Query;
Fragment = absoluteUri.Fragment;
}

public ImplicitString Scheme { get; }

public ImplicitString Authority { get; }

public ImplicitString Path { get; }

public IImmutableDictionary<ImplicitString, ImplicitString> Query { get; }

public ImplicitString Fragment { get; }

public bool IsRelative => !Scheme;

public override string ToString() => string.Join(string.Empty, GetComponents());

private IEnumerable<string> GetComponents()
{
if (Scheme)
{
yield return $"{Scheme}:";
}

if (Authority)
{
yield return $"//{Authority}";
}

yield return Path;

if (Query.Any())
{
var queryPairs =
Query
.OrderBy(x => (string)x.Key, StringComparer.OrdinalIgnoreCase)
.Select(x => $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}");
yield return $"?{string.Join("&", queryPairs)}";
}

if (Fragment)
{
yield return $"#{Fragment}";
}
}

#region IEquatable

public bool Equals(SimpleUri other) => Comparer.Equals(this, other);

public bool Equals(string other) => Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is SimpleUri uri && Equals(uri);

public override int GetHashCode() => Comparer.GetHashCode(this);

#endregion

#region operators

public static implicit operator SimpleUri(string uri) => new SimpleUri(uri);

public static implicit operator string(SimpleUri uri) => uri.ToString();

public static SimpleUri operator +(SimpleUri absoluteUri, SimpleUri relativeUri) => new SimpleUri(absoluteUri, relativeUri);

#endregion
}


In this experiment I use a new helper for the first time which is the ImplicitString. Its purpose is to be able to use a string as a conditional without having to write any of the string.IsX all over the place.



public class ImplicitString : IEquatable<ImplicitString>
{
public ImplicitString(string value) => Value = value;

[AutoEqualityProperty]
public string Value { get; }

public override string ToString() => Value;

public static implicit operator ImplicitString(string value) => new ImplicitString(value);

public static implicit operator ImplicitString(Group group) => group.Value;

public static implicit operator string(ImplicitString value) => value.ToString();

public static implicit operator bool(ImplicitString value) => !string.IsNullOrWhiteSpace(value);

#region IEquatable

public bool Equals(ImplicitString other) => AutoEquality<ImplicitString>.Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is ImplicitString str && Equals(str);

public override int GetHashCode() => AutoEquality<ImplicitString>.Comparer.GetHashCode(this);

#endregion
}


Example



I tested it with use-cases I currently need it for and it works fine:



new SimpleUri("scheme://authority/p/a/t/h?query#fragment").Dump();
new SimpleUri("scheme:p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h").Dump();
new SimpleUri("file:c:/p/a/t/h").Dump();




What do you say? Is this an optimal implementation or can it be improved?










share|improve this question
























  • The EqualityComparerFactory can be found here.
    – t3chb0t
    Dec 16 at 18:07










  • Downvoted... how so?
    – t3chb0t
    Dec 17 at 12:49










  • FWIW I’m mildly surprised you didn’t extend URI to create SimpleUri. Surely it would be easier and more robust.
    – RubberDuck
    Dec 19 at 22:53










  • @RubberDuck I can still do this ;-] and I actually already was there but the Uri is not as great as one would think. It throws unexplained exceptions all over the place. Values are spread over several strange and seemingly redundant properties. I might use it later for some edge cases that I'm to lazy to implement myself but generally it's terrible. On top of it it has a super-confusing system for registering new parsers which will break any dependency-injection. Uri is more trouble than help.
    – t3chb0t
    2 days ago


















4














I needed some data structure for holding Uris so I thought I try the Uri class. It quickly turned out that it's very impractical because:




  • it's missing an implicit cast from/to string

  • it doesn't automatically recognize uris as absolute or relative without specifying the UriKind

  • it doesn't parse the query-string so it cannot compare uris reliably when key-value pairs are in different order

  • it's not easy to combine an absolute uri with a relative one


Working with it is just too inconvenient. As a solution I created my own SimpleUri that should solve these issues. It doesn't support everything yet because I don't need it right now but adding a couple of more regexes later should not be a problem.



public class SimpleUri : IEquatable<SimpleUri>, IEquatable<string>
{
// https://regex101.com/r/sd288W/1
// using 'new' for _nicer_ syntax
private static readonly string UriPattern = string.Join(string.Empty, new
{
/* language=regexp */ @"^(?:(?<scheme>w+):)?",
/* language=regexp */ @"(?://(?<authority>w+))?",
/* language=regexp */ @"(?<path>[a-z0-9/:]+)",
/* language=regexp */ @"(?:?(?<query>[a-z0-9=&]+))?",
/* language=regexp */ @"(?:#(?<fragment>[a-z0-9]+))?"
});

public static readonly IEqualityComparer<SimpleUri> Comparer = EqualityComparerFactory<SimpleUri>.Create
(
equals: (x, y) => StringComparer.OrdinalIgnoreCase.Equals(x, y),
getHashCode: (obj) => StringComparer.OrdinalIgnoreCase.GetHashCode(obj)
);

public SimpleUri([NotNull] string uri)
{
if (uri == null) throw new ArgumentNullException(nameof(uri));

var uriMatch = Regex.Match
(
uri,
UriPattern,
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
);

if (!uriMatch.Success)
{
throw new ArgumentException(paramName: nameof(uri), message: $"'{uri}' is not a valid Uri.");
}

Scheme = uriMatch.Groups["scheme"];
Authority = uriMatch.Groups["authority"];
Path = uriMatch.Groups["path"];
Query =
uriMatch.Groups["query"].Success
? Regex
.Matches
(
uriMatch.Groups["query"].Value,
@"(?:^|&)(?<key>[a-z0-9]+)(?:=(?<value>[a-z0-9]+))?",
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
)
.Cast<Match>()
.ToImmutableDictionary
(
m => (ImplicitString)m.Groups["key"],
m => (ImplicitString)m.Groups["value"]
)
: ImmutableDictionary<ImplicitString, ImplicitString>.Empty;
Fragment = uriMatch.Groups["fragment"];
}

public SimpleUri(SimpleUri absoluteUri, SimpleUri relativeUri)
{
if (absoluteUri.IsRelative) throw new ArgumentException($"{nameof(absoluteUri)} must be an absolute Uri.");
if (!relativeUri.IsRelative) throw new ArgumentException($"{nameof(relativeUri)} must be a relative Uri.");

Scheme = absoluteUri.Scheme;
Authority = absoluteUri.Authority;
Path = absoluteUri.Path.Value.TrimEnd('/') + "/" + relativeUri.Path.Value.TrimStart('/');
Query = absoluteUri.Query;
Fragment = absoluteUri.Fragment;
}

public ImplicitString Scheme { get; }

public ImplicitString Authority { get; }

public ImplicitString Path { get; }

public IImmutableDictionary<ImplicitString, ImplicitString> Query { get; }

public ImplicitString Fragment { get; }

public bool IsRelative => !Scheme;

public override string ToString() => string.Join(string.Empty, GetComponents());

private IEnumerable<string> GetComponents()
{
if (Scheme)
{
yield return $"{Scheme}:";
}

if (Authority)
{
yield return $"//{Authority}";
}

yield return Path;

if (Query.Any())
{
var queryPairs =
Query
.OrderBy(x => (string)x.Key, StringComparer.OrdinalIgnoreCase)
.Select(x => $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}");
yield return $"?{string.Join("&", queryPairs)}";
}

if (Fragment)
{
yield return $"#{Fragment}";
}
}

#region IEquatable

public bool Equals(SimpleUri other) => Comparer.Equals(this, other);

public bool Equals(string other) => Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is SimpleUri uri && Equals(uri);

public override int GetHashCode() => Comparer.GetHashCode(this);

#endregion

#region operators

public static implicit operator SimpleUri(string uri) => new SimpleUri(uri);

public static implicit operator string(SimpleUri uri) => uri.ToString();

public static SimpleUri operator +(SimpleUri absoluteUri, SimpleUri relativeUri) => new SimpleUri(absoluteUri, relativeUri);

#endregion
}


In this experiment I use a new helper for the first time which is the ImplicitString. Its purpose is to be able to use a string as a conditional without having to write any of the string.IsX all over the place.



public class ImplicitString : IEquatable<ImplicitString>
{
public ImplicitString(string value) => Value = value;

[AutoEqualityProperty]
public string Value { get; }

public override string ToString() => Value;

public static implicit operator ImplicitString(string value) => new ImplicitString(value);

public static implicit operator ImplicitString(Group group) => group.Value;

public static implicit operator string(ImplicitString value) => value.ToString();

public static implicit operator bool(ImplicitString value) => !string.IsNullOrWhiteSpace(value);

#region IEquatable

public bool Equals(ImplicitString other) => AutoEquality<ImplicitString>.Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is ImplicitString str && Equals(str);

public override int GetHashCode() => AutoEquality<ImplicitString>.Comparer.GetHashCode(this);

#endregion
}


Example



I tested it with use-cases I currently need it for and it works fine:



new SimpleUri("scheme://authority/p/a/t/h?query#fragment").Dump();
new SimpleUri("scheme:p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h").Dump();
new SimpleUri("file:c:/p/a/t/h").Dump();




What do you say? Is this an optimal implementation or can it be improved?










share|improve this question
























  • The EqualityComparerFactory can be found here.
    – t3chb0t
    Dec 16 at 18:07










  • Downvoted... how so?
    – t3chb0t
    Dec 17 at 12:49










  • FWIW I’m mildly surprised you didn’t extend URI to create SimpleUri. Surely it would be easier and more robust.
    – RubberDuck
    Dec 19 at 22:53










  • @RubberDuck I can still do this ;-] and I actually already was there but the Uri is not as great as one would think. It throws unexplained exceptions all over the place. Values are spread over several strange and seemingly redundant properties. I might use it later for some edge cases that I'm to lazy to implement myself but generally it's terrible. On top of it it has a super-confusing system for registering new parsers which will break any dependency-injection. Uri is more trouble than help.
    – t3chb0t
    2 days ago
















4












4








4


1





I needed some data structure for holding Uris so I thought I try the Uri class. It quickly turned out that it's very impractical because:




  • it's missing an implicit cast from/to string

  • it doesn't automatically recognize uris as absolute or relative without specifying the UriKind

  • it doesn't parse the query-string so it cannot compare uris reliably when key-value pairs are in different order

  • it's not easy to combine an absolute uri with a relative one


Working with it is just too inconvenient. As a solution I created my own SimpleUri that should solve these issues. It doesn't support everything yet because I don't need it right now but adding a couple of more regexes later should not be a problem.



public class SimpleUri : IEquatable<SimpleUri>, IEquatable<string>
{
// https://regex101.com/r/sd288W/1
// using 'new' for _nicer_ syntax
private static readonly string UriPattern = string.Join(string.Empty, new
{
/* language=regexp */ @"^(?:(?<scheme>w+):)?",
/* language=regexp */ @"(?://(?<authority>w+))?",
/* language=regexp */ @"(?<path>[a-z0-9/:]+)",
/* language=regexp */ @"(?:?(?<query>[a-z0-9=&]+))?",
/* language=regexp */ @"(?:#(?<fragment>[a-z0-9]+))?"
});

public static readonly IEqualityComparer<SimpleUri> Comparer = EqualityComparerFactory<SimpleUri>.Create
(
equals: (x, y) => StringComparer.OrdinalIgnoreCase.Equals(x, y),
getHashCode: (obj) => StringComparer.OrdinalIgnoreCase.GetHashCode(obj)
);

public SimpleUri([NotNull] string uri)
{
if (uri == null) throw new ArgumentNullException(nameof(uri));

var uriMatch = Regex.Match
(
uri,
UriPattern,
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
);

if (!uriMatch.Success)
{
throw new ArgumentException(paramName: nameof(uri), message: $"'{uri}' is not a valid Uri.");
}

Scheme = uriMatch.Groups["scheme"];
Authority = uriMatch.Groups["authority"];
Path = uriMatch.Groups["path"];
Query =
uriMatch.Groups["query"].Success
? Regex
.Matches
(
uriMatch.Groups["query"].Value,
@"(?:^|&)(?<key>[a-z0-9]+)(?:=(?<value>[a-z0-9]+))?",
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
)
.Cast<Match>()
.ToImmutableDictionary
(
m => (ImplicitString)m.Groups["key"],
m => (ImplicitString)m.Groups["value"]
)
: ImmutableDictionary<ImplicitString, ImplicitString>.Empty;
Fragment = uriMatch.Groups["fragment"];
}

public SimpleUri(SimpleUri absoluteUri, SimpleUri relativeUri)
{
if (absoluteUri.IsRelative) throw new ArgumentException($"{nameof(absoluteUri)} must be an absolute Uri.");
if (!relativeUri.IsRelative) throw new ArgumentException($"{nameof(relativeUri)} must be a relative Uri.");

Scheme = absoluteUri.Scheme;
Authority = absoluteUri.Authority;
Path = absoluteUri.Path.Value.TrimEnd('/') + "/" + relativeUri.Path.Value.TrimStart('/');
Query = absoluteUri.Query;
Fragment = absoluteUri.Fragment;
}

public ImplicitString Scheme { get; }

public ImplicitString Authority { get; }

public ImplicitString Path { get; }

public IImmutableDictionary<ImplicitString, ImplicitString> Query { get; }

public ImplicitString Fragment { get; }

public bool IsRelative => !Scheme;

public override string ToString() => string.Join(string.Empty, GetComponents());

private IEnumerable<string> GetComponents()
{
if (Scheme)
{
yield return $"{Scheme}:";
}

if (Authority)
{
yield return $"//{Authority}";
}

yield return Path;

if (Query.Any())
{
var queryPairs =
Query
.OrderBy(x => (string)x.Key, StringComparer.OrdinalIgnoreCase)
.Select(x => $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}");
yield return $"?{string.Join("&", queryPairs)}";
}

if (Fragment)
{
yield return $"#{Fragment}";
}
}

#region IEquatable

public bool Equals(SimpleUri other) => Comparer.Equals(this, other);

public bool Equals(string other) => Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is SimpleUri uri && Equals(uri);

public override int GetHashCode() => Comparer.GetHashCode(this);

#endregion

#region operators

public static implicit operator SimpleUri(string uri) => new SimpleUri(uri);

public static implicit operator string(SimpleUri uri) => uri.ToString();

public static SimpleUri operator +(SimpleUri absoluteUri, SimpleUri relativeUri) => new SimpleUri(absoluteUri, relativeUri);

#endregion
}


In this experiment I use a new helper for the first time which is the ImplicitString. Its purpose is to be able to use a string as a conditional without having to write any of the string.IsX all over the place.



public class ImplicitString : IEquatable<ImplicitString>
{
public ImplicitString(string value) => Value = value;

[AutoEqualityProperty]
public string Value { get; }

public override string ToString() => Value;

public static implicit operator ImplicitString(string value) => new ImplicitString(value);

public static implicit operator ImplicitString(Group group) => group.Value;

public static implicit operator string(ImplicitString value) => value.ToString();

public static implicit operator bool(ImplicitString value) => !string.IsNullOrWhiteSpace(value);

#region IEquatable

public bool Equals(ImplicitString other) => AutoEquality<ImplicitString>.Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is ImplicitString str && Equals(str);

public override int GetHashCode() => AutoEquality<ImplicitString>.Comparer.GetHashCode(this);

#endregion
}


Example



I tested it with use-cases I currently need it for and it works fine:



new SimpleUri("scheme://authority/p/a/t/h?query#fragment").Dump();
new SimpleUri("scheme:p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h").Dump();
new SimpleUri("file:c:/p/a/t/h").Dump();




What do you say? Is this an optimal implementation or can it be improved?










share|improve this question















I needed some data structure for holding Uris so I thought I try the Uri class. It quickly turned out that it's very impractical because:




  • it's missing an implicit cast from/to string

  • it doesn't automatically recognize uris as absolute or relative without specifying the UriKind

  • it doesn't parse the query-string so it cannot compare uris reliably when key-value pairs are in different order

  • it's not easy to combine an absolute uri with a relative one


Working with it is just too inconvenient. As a solution I created my own SimpleUri that should solve these issues. It doesn't support everything yet because I don't need it right now but adding a couple of more regexes later should not be a problem.



public class SimpleUri : IEquatable<SimpleUri>, IEquatable<string>
{
// https://regex101.com/r/sd288W/1
// using 'new' for _nicer_ syntax
private static readonly string UriPattern = string.Join(string.Empty, new
{
/* language=regexp */ @"^(?:(?<scheme>w+):)?",
/* language=regexp */ @"(?://(?<authority>w+))?",
/* language=regexp */ @"(?<path>[a-z0-9/:]+)",
/* language=regexp */ @"(?:?(?<query>[a-z0-9=&]+))?",
/* language=regexp */ @"(?:#(?<fragment>[a-z0-9]+))?"
});

public static readonly IEqualityComparer<SimpleUri> Comparer = EqualityComparerFactory<SimpleUri>.Create
(
equals: (x, y) => StringComparer.OrdinalIgnoreCase.Equals(x, y),
getHashCode: (obj) => StringComparer.OrdinalIgnoreCase.GetHashCode(obj)
);

public SimpleUri([NotNull] string uri)
{
if (uri == null) throw new ArgumentNullException(nameof(uri));

var uriMatch = Regex.Match
(
uri,
UriPattern,
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
);

if (!uriMatch.Success)
{
throw new ArgumentException(paramName: nameof(uri), message: $"'{uri}' is not a valid Uri.");
}

Scheme = uriMatch.Groups["scheme"];
Authority = uriMatch.Groups["authority"];
Path = uriMatch.Groups["path"];
Query =
uriMatch.Groups["query"].Success
? Regex
.Matches
(
uriMatch.Groups["query"].Value,
@"(?:^|&)(?<key>[a-z0-9]+)(?:=(?<value>[a-z0-9]+))?",
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture
)
.Cast<Match>()
.ToImmutableDictionary
(
m => (ImplicitString)m.Groups["key"],
m => (ImplicitString)m.Groups["value"]
)
: ImmutableDictionary<ImplicitString, ImplicitString>.Empty;
Fragment = uriMatch.Groups["fragment"];
}

public SimpleUri(SimpleUri absoluteUri, SimpleUri relativeUri)
{
if (absoluteUri.IsRelative) throw new ArgumentException($"{nameof(absoluteUri)} must be an absolute Uri.");
if (!relativeUri.IsRelative) throw new ArgumentException($"{nameof(relativeUri)} must be a relative Uri.");

Scheme = absoluteUri.Scheme;
Authority = absoluteUri.Authority;
Path = absoluteUri.Path.Value.TrimEnd('/') + "/" + relativeUri.Path.Value.TrimStart('/');
Query = absoluteUri.Query;
Fragment = absoluteUri.Fragment;
}

public ImplicitString Scheme { get; }

public ImplicitString Authority { get; }

public ImplicitString Path { get; }

public IImmutableDictionary<ImplicitString, ImplicitString> Query { get; }

public ImplicitString Fragment { get; }

public bool IsRelative => !Scheme;

public override string ToString() => string.Join(string.Empty, GetComponents());

private IEnumerable<string> GetComponents()
{
if (Scheme)
{
yield return $"{Scheme}:";
}

if (Authority)
{
yield return $"//{Authority}";
}

yield return Path;

if (Query.Any())
{
var queryPairs =
Query
.OrderBy(x => (string)x.Key, StringComparer.OrdinalIgnoreCase)
.Select(x => $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}");
yield return $"?{string.Join("&", queryPairs)}";
}

if (Fragment)
{
yield return $"#{Fragment}";
}
}

#region IEquatable

public bool Equals(SimpleUri other) => Comparer.Equals(this, other);

public bool Equals(string other) => Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is SimpleUri uri && Equals(uri);

public override int GetHashCode() => Comparer.GetHashCode(this);

#endregion

#region operators

public static implicit operator SimpleUri(string uri) => new SimpleUri(uri);

public static implicit operator string(SimpleUri uri) => uri.ToString();

public static SimpleUri operator +(SimpleUri absoluteUri, SimpleUri relativeUri) => new SimpleUri(absoluteUri, relativeUri);

#endregion
}


In this experiment I use a new helper for the first time which is the ImplicitString. Its purpose is to be able to use a string as a conditional without having to write any of the string.IsX all over the place.



public class ImplicitString : IEquatable<ImplicitString>
{
public ImplicitString(string value) => Value = value;

[AutoEqualityProperty]
public string Value { get; }

public override string ToString() => Value;

public static implicit operator ImplicitString(string value) => new ImplicitString(value);

public static implicit operator ImplicitString(Group group) => group.Value;

public static implicit operator string(ImplicitString value) => value.ToString();

public static implicit operator bool(ImplicitString value) => !string.IsNullOrWhiteSpace(value);

#region IEquatable

public bool Equals(ImplicitString other) => AutoEquality<ImplicitString>.Comparer.Equals(this, other);

public override bool Equals(object obj) => obj is ImplicitString str && Equals(str);

public override int GetHashCode() => AutoEquality<ImplicitString>.Comparer.GetHashCode(this);

#endregion
}


Example



I tested it with use-cases I currently need it for and it works fine:



new SimpleUri("scheme://authority/p/a/t/h?query#fragment").Dump();
new SimpleUri("scheme:p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h?q1=v1&q2=v2&q3#fragment").Dump();
new SimpleUri("p/a/t/h").Dump();
new SimpleUri("file:c:/p/a/t/h").Dump();




What do you say? Is this an optimal implementation or can it be improved?







c# parsing regex dto






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 17 at 7:21









janos

97.1k12124350




97.1k12124350










asked Dec 16 at 18:03









t3chb0t

34k746111




34k746111












  • The EqualityComparerFactory can be found here.
    – t3chb0t
    Dec 16 at 18:07










  • Downvoted... how so?
    – t3chb0t
    Dec 17 at 12:49










  • FWIW I’m mildly surprised you didn’t extend URI to create SimpleUri. Surely it would be easier and more robust.
    – RubberDuck
    Dec 19 at 22:53










  • @RubberDuck I can still do this ;-] and I actually already was there but the Uri is not as great as one would think. It throws unexplained exceptions all over the place. Values are spread over several strange and seemingly redundant properties. I might use it later for some edge cases that I'm to lazy to implement myself but generally it's terrible. On top of it it has a super-confusing system for registering new parsers which will break any dependency-injection. Uri is more trouble than help.
    – t3chb0t
    2 days ago




















  • The EqualityComparerFactory can be found here.
    – t3chb0t
    Dec 16 at 18:07










  • Downvoted... how so?
    – t3chb0t
    Dec 17 at 12:49










  • FWIW I’m mildly surprised you didn’t extend URI to create SimpleUri. Surely it would be easier and more robust.
    – RubberDuck
    Dec 19 at 22:53










  • @RubberDuck I can still do this ;-] and I actually already was there but the Uri is not as great as one would think. It throws unexplained exceptions all over the place. Values are spread over several strange and seemingly redundant properties. I might use it later for some edge cases that I'm to lazy to implement myself but generally it's terrible. On top of it it has a super-confusing system for registering new parsers which will break any dependency-injection. Uri is more trouble than help.
    – t3chb0t
    2 days ago


















The EqualityComparerFactory can be found here.
– t3chb0t
Dec 16 at 18:07




The EqualityComparerFactory can be found here.
– t3chb0t
Dec 16 at 18:07












Downvoted... how so?
– t3chb0t
Dec 17 at 12:49




Downvoted... how so?
– t3chb0t
Dec 17 at 12:49












FWIW I’m mildly surprised you didn’t extend URI to create SimpleUri. Surely it would be easier and more robust.
– RubberDuck
Dec 19 at 22:53




FWIW I’m mildly surprised you didn’t extend URI to create SimpleUri. Surely it would be easier and more robust.
– RubberDuck
Dec 19 at 22:53












@RubberDuck I can still do this ;-] and I actually already was there but the Uri is not as great as one would think. It throws unexplained exceptions all over the place. Values are spread over several strange and seemingly redundant properties. I might use it later for some edge cases that I'm to lazy to implement myself but generally it's terrible. On top of it it has a super-confusing system for registering new parsers which will break any dependency-injection. Uri is more trouble than help.
– t3chb0t
2 days ago






@RubberDuck I can still do this ;-] and I actually already was there but the Uri is not as great as one would think. It throws unexplained exceptions all over the place. Values are spread over several strange and seemingly redundant properties. I might use it later for some edge cases that I'm to lazy to implement myself but generally it's terrible. On top of it it has a super-confusing system for registering new parsers which will break any dependency-injection. Uri is more trouble than help.
– t3chb0t
2 days ago












1 Answer
1






active

oldest

votes


















3














Instead of this:




$"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}"



I would find it easier to understand this way:



$"{x.Key}{(x.Value ? $"={x.Value}" : string.Empty)}"




Instead of testing-by-printing, why not have proper unit tests for this?
(So I don't have to read the output after every change and re-convince myself that it's still good.)





I'm a bit surprised that the class doesn't handle URI with domain names containing a dot, for example https://stackoverflow.com/somepath.





It might be an interesting feature to add,
when !uriMatch.Success, to check if the URI could actually be parsed by the standard Uri class.
That is, give a more clear signal to users of the class,
whether the URI is invalid, or it's just using some pattern not-yet-supported by SimpleUri.






share|improve this answer





















    Your Answer





    StackExchange.ifUsing("editor", function () {
    return StackExchange.using("mathjaxEditing", function () {
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    });
    });
    }, "mathjax-editing");

    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "196"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209777%2freplacing-uri-with-a-more-convenient-class%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    3














    Instead of this:




    $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}"



    I would find it easier to understand this way:



    $"{x.Key}{(x.Value ? $"={x.Value}" : string.Empty)}"




    Instead of testing-by-printing, why not have proper unit tests for this?
    (So I don't have to read the output after every change and re-convince myself that it's still good.)





    I'm a bit surprised that the class doesn't handle URI with domain names containing a dot, for example https://stackoverflow.com/somepath.





    It might be an interesting feature to add,
    when !uriMatch.Success, to check if the URI could actually be parsed by the standard Uri class.
    That is, give a more clear signal to users of the class,
    whether the URI is invalid, or it's just using some pattern not-yet-supported by SimpleUri.






    share|improve this answer


























      3














      Instead of this:




      $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}"



      I would find it easier to understand this way:



      $"{x.Key}{(x.Value ? $"={x.Value}" : string.Empty)}"




      Instead of testing-by-printing, why not have proper unit tests for this?
      (So I don't have to read the output after every change and re-convince myself that it's still good.)





      I'm a bit surprised that the class doesn't handle URI with domain names containing a dot, for example https://stackoverflow.com/somepath.





      It might be an interesting feature to add,
      when !uriMatch.Success, to check if the URI could actually be parsed by the standard Uri class.
      That is, give a more clear signal to users of the class,
      whether the URI is invalid, or it's just using some pattern not-yet-supported by SimpleUri.






      share|improve this answer
























        3












        3








        3






        Instead of this:




        $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}"



        I would find it easier to understand this way:



        $"{x.Key}{(x.Value ? $"={x.Value}" : string.Empty)}"




        Instead of testing-by-printing, why not have proper unit tests for this?
        (So I don't have to read the output after every change and re-convince myself that it's still good.)





        I'm a bit surprised that the class doesn't handle URI with domain names containing a dot, for example https://stackoverflow.com/somepath.





        It might be an interesting feature to add,
        when !uriMatch.Success, to check if the URI could actually be parsed by the standard Uri class.
        That is, give a more clear signal to users of the class,
        whether the URI is invalid, or it's just using some pattern not-yet-supported by SimpleUri.






        share|improve this answer












        Instead of this:




        $"{x.Key}{(x.Value ? "=" : string.Empty)}{x.Value}"



        I would find it easier to understand this way:



        $"{x.Key}{(x.Value ? $"={x.Value}" : string.Empty)}"




        Instead of testing-by-printing, why not have proper unit tests for this?
        (So I don't have to read the output after every change and re-convince myself that it's still good.)





        I'm a bit surprised that the class doesn't handle URI with domain names containing a dot, for example https://stackoverflow.com/somepath.





        It might be an interesting feature to add,
        when !uriMatch.Success, to check if the URI could actually be parsed by the standard Uri class.
        That is, give a more clear signal to users of the class,
        whether the URI is invalid, or it's just using some pattern not-yet-supported by SimpleUri.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Dec 17 at 7:44









        janos

        97.1k12124350




        97.1k12124350






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Code Review Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            Use MathJax to format equations. MathJax reference.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209777%2freplacing-uri-with-a-more-convenient-class%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Сан-Квентин

            8-я гвардейская общевойсковая армия

            Алькесар