System.Uri & hashtables: Is it a bug?

  • Thread starter Thread starter Michael Schollmeyer
  • Start date Start date
M

Michael Schollmeyer

Hello,

The following code writes the text string:

Uri uri1 = new Uri("http://www.here.net/aplace?param=one");
Uri uri2 = new Uri("http://www.here.net/aplace?param=two");
int h1 = uri1.GetHashCode();
int h2 = uri2.GetHashCode();
bool e = uri1.Equals(uri2);
if (e == true && h1 != h2)
{
System.Console.WriteLine("Is is a bug or is it a feature?");
}

I thought the hash codes must be the same when two instances are equal. BTW,
I found this because Uri's seem to be unusable as keys in a hashtable.

I am using the following versions:
Microsoft (R) Visual C# .NET Compiler version 7.10.3052.4
for Microsoft (R) .NET Framework version 1.1.4322

Cheers,
Michael
 
Michael Schollmeyer said:
The following code writes the text string:

Uri uri1 = new Uri("http://www.here.net/aplace?param=one");
Uri uri2 = new Uri("http://www.here.net/aplace?param=two");
int h1 = uri1.GetHashCode();
int h2 = uri2.GetHashCode();
bool e = uri1.Equals(uri2);
if (e == true && h1 != h2)
{
System.Console.WriteLine("Is is a bug or is it a feature?");
}

I thought the hash codes must be the same when two instances are equal. BTW,
I found this because Uri's seem to be unusable as keys in a hashtable.

Yes, that looks like a bug to me.
 
Michael Schollmeyer said:
Hello,

The following code writes the text string:

Uri uri1 = new Uri("http://www.here.net/aplace?param=one");
Uri uri2 = new Uri("http://www.here.net/aplace?param=two");
int h1 = uri1.GetHashCode();
int h2 = uri2.GetHashCode();
bool e = uri1.Equals(uri2);
if (e == true && h1 != h2)
{
System.Console.WriteLine("Is is a bug or is it a feature?");
}

I thought the hash codes must be the same when two instances are equal. BTW,
I found this because Uri's seem to be unusable as keys in a hashtable.

I am using the following versions:
Microsoft (R) Visual C# .NET Compiler version 7.10.3052.4
for Microsoft (R) .NET Framework version 1.1.4322

Cheers,
Michael

That's unexpected. The docs for Uri.Equals explain that it doesn't consider
fragments, but doesn't say anything about ignoring query strings. If you
rely on Equals to consider query data, you can certainly create a derived
class to provide the desired behavior:

class MyUri : Uri
{
public MyUri(string uri) : base(uri)
{
}

public override bool Equals(object comparand)
{
if (!base.Equals(comparand))
return false;

Uri uri = comparand as Uri;
if (uri == null)
return false;

return this.PathAndQuery == uri.PathAndQuery;
}

public override int GetHashCode()
{
return base.GetHashCode();
}
}

This doesn't work quite like Uri.Equals in the way it handles string
arguments, but you can extend it however you like.
 
Michael Schollmeyer said:
Hello,

The following code writes the text string:

Uri uri1 = new Uri("http://www.here.net/aplace?param=one");
Uri uri2 = new Uri("http://www.here.net/aplace?param=two");
int h1 = uri1.GetHashCode();
int h2 = uri2.GetHashCode();
bool e = uri1.Equals(uri2);
if (e == true && h1 != h2)
{
System.Console.WriteLine("Is is a bug or is it a feature?");
}

I thought the hash codes must be the same when two instances are equal. BTW,
I found this because Uri's seem to be unusable as keys in a hashtable.

I am using the following versions:
Microsoft (R) Visual C# .NET Compiler version 7.10.3052.4
for Microsoft (R) .NET Framework version 1.1.4322

Cheers,
Michael

That's unexpected. The docs for Uri.Equals explain that it doesn't consider
fragments, but doesn't say anything about ignoring query strings. If you
rely on Equals to consider query data, you can certainly create a derived
class to provide the desired behavior:

class MyUri : Uri
{
public MyUri(string uri) : base(uri)
{
}

public override bool Equals(object comparand)
{
if (!base.Equals(comparand))
return false;

Uri uri = comparand as Uri;
if (uri == null)
return false;

return this.PathAndQuery == uri.PathAndQuery;
}

public override int GetHashCode()
{
return base.GetHashCode();
}
}

This doesn't work quite like Uri.Equals in the way it handles string
arguments, but you can extend it however you like.
 
Hmmm i'm not really sure what the mentality or some of the arguments
for/against the querystring being considered as part of the hashcode, but
I'm sure there's a good reason...

Bret's suggestion should work for you if need be...
 
Michael said:
Hello,

The following code writes the text string:

Uri uri1 = new Uri("http://www.here.net/aplace?param=one");
Uri uri2 = new Uri("http://www.here.net/aplace?param=two");
int h1 = uri1.GetHashCode();
int h2 = uri2.GetHashCode();
bool e = uri1.Equals(uri2);
if (e == true && h1 != h2)
{
System.Console.WriteLine("Is is a bug or is it a feature?");
}

I thought the hash codes must be the same when two instances are equal. BTW,
I found this because Uri's seem to be unusable as keys in a hashtable.

This looks like a bug.

Uri.Equals() seems to key off of the Scheme, HostType (host name or IP
addr), Port (only in Framework 1.1) and AbsolutePath properties.

Uri.GetHashCode() derives its result from the AbsoluteUri property.
 
Hello Bret~

I am using a hashtable that maps instances or Uri to objects. I cannot
derive the key's type from Uri because the object construction is outside my
code domain.

I also believe that there is a more general problem with using instances of
Uri as keys into hashtables. Look at the following code:

Uri u0 = new Uri("http://www.here.net/adoc?param=12a7665f334edd32");
Uri u1 = new Uri("http://www.here.net/adoc?param=1543ffefa23c34b5");
Uri u2 = new Uri("http://www.here.net/adoc?param=68e20c473edb271d");
Hashtable h = new Hashtable();
h[u0] = 1;
h[u1] = 2;
h[u2] = 3;
int a = (int)h[u0];
int b = (int)h[u1];
int c = (int)h[u2];
int count = h.Count;

Run the code and look at the values for a, b, c, and count. You will be more
than surprised!
BTW, the numbers I've chosen after param= are NOT random...

IMHO, people should be discouraged from using Uri as keys into hashtables
without explicitly specifying an IHashCodeProvider because the current
implementation of Uri breaks a vital rule: When o1.Equals(o2) is true, then
o1.GetHashCode() MUST be equal to o2.GetHashCode(). Everything else is a
bug.
 
Back
Top