There are documents of various types on the web. For simplicity, however, we are now only interested in HTML and RSS documents. We will describe this fact in three classes. One is abstract and describes what HTML and RSS have in common, while the other two describe the specific features of the document. What exactly they would describe is not important, I want to outline a situation in which the use of inheritance makes sense.
public abstract class Document {
public Document(Uri url) {
Url = url;
}
public Uri Url { get; private set; }
}
public class HtmlDocument : Document {
public HtmlDocument(Uri url) : base(url) { }
}
public class RssDocument : Document {
public RssDocument(Uri url) : base(url) { }
}
All documents have one thing in common – a URL address – a unique identifier on the web. Let’s say we use class HtmlDocument
to process some data and we are interested in, or rather we want to influence the behavior with respect to data structures like List<T>
. Let’s start with the default behavior.
var list = new List<Document>();
list.Add(new HtmlDocument(new Uri("http://dajbych.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://dajbych.net")));
The value of the result variable is false. Two classes are created in the program, two different references, and object.ReferenceEquals
returns false
for them.
If we want to make .NET interested not only in the conformity of references, but also in the conformity of data, we implement the Document
class of the IEquatable<T>
interface:
public abstract class Document : IEquatable<Document> {
public Document(Uri url) {
Url = url;
}
public Uri Url { get; private set; }
public bool Equals(Document other) {
if (this.Url != null && other.Url != null) {
return this.Url.Equals(other.Url);
} else {
return base.Equals(other);
}
}
}
After that, List<T>
will behave differently:
var list = new List<Document>();
list.Add(new HtmlDocument(new Uri("http://dajbych.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://dajbych.net")));
The value of variable result
is now true
. Great, the basic problem is solved. But this code is still a long way from practical usability.
First of all, Document
is only a base class. However, after being removed from List<T>
, I want to work with class HtmlDocument
. Casting is the source of errors. I actually want List<HtmlDocument>
. I have discounted List<Document>
only as a concession to IEquatable<Document>
. Isn't that wrong?
var list = new List<HtmlDocument>();
list.Add(new HtmlDocument(new Uri("http://dajbych.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://dajbych.net")));
The value of the variable result
is false
because .NET does not look for IEquatable<T>
in the base classes. This inheritance needs to be programmed:
public class HtmlDocument : Document, IEquatable<HtmlDocument> {
public HtmlDocument(Uri url) : base(url) { }
public bool Equals(HtmlDocument other) {
return base.Equals(other);
}
}
Now everything is behaving as we expect.
var list = new List<HtmlDocument>();
list.Add(new HtmlDocument(new Uri("http://feedviewer.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://feedviewer.net")));
The value of variable result
is true
. The logic of comparison is in the base class and common to all children.