Summary
Let’s say I have two C# 4.0 classes, one inheriting from the other:
class ParentKey {}
class ChildKey : ParentKey {}
I want the compiler to issue an error if I try this:
ChildKey c = new ChildKey();
ParentKey p = c; // I want compiler error here!
Essentially, I want to use inheritance for reusability purposes, but I want to avoid polymorphic behavior (or more specifically, assign compatibility) that normally comes with it. Similar to C++ private inheritance.
Example
Specifically, I’d like to avoid accidentally mixing ParentKey and ChildKey when used as keys of some container (since their implementations of GetHashCode() or Equals() might be incompatible). For example:
Dictionary<ParentKey, object> d = new Dictionary<ParentKey, object>();
d.Add(new ChildKey(), new object()); // I want compiler error here!
What I Tried
Now, I know I can use composition to avoid the inheritance altogether, but I’d like to avoid the verbosity that comes with this solution (my ParentKey can be quite complex, and there may be many levels of inheritance hierarchy).
Another solution is to always use tailor-made IEqualityComparer, or to explicitly create new ParentKey based on the ChildKey prior passing to the container, but both of these are easy to forget, and may be comparatively hard to diagnose at run-time.
Attempting to make the conversion explicit…
class ChildKey : ParentKey {
public static explicit operator ParentKey(ChildKey c) {
// ...
}
}
…yielded compiler error CS0553: user-defined conversions to or from a base class are not allowed.
Struct inheritance would be ideal here (so the “end” portion of ChildKey is “cut-off” when passed to something that is declared as ParentKey), but this is not supported in C# either.
Am I missing something obvious here? Any ideas? Thanks.
You’re working directly against the by-design purpose of the type system, which is to make it always possible to assign a more-derived type to a variable of a less-derived type. (Moreoever: suppose you did somehow manage to prevent implicit reference conversions from Derived to Base — what stops you from converting Derived to object and then explicitly converting object to Base? It seems perverse to prohibit something at compile time that we cannot prevent at runtime.)
I agree that from a language design perspective, it is possible to create a language which avoids conflating code reuse via inheritance with subtype polymorphism. However, we chose to conflate those two things a long, long time ago. You’re going to have to either live with that choice, or use a different language that gives you the feature you want. (*)
My advice: stop spitting into the wind. Either use composition, or carefully craft your Equals and GetHashCode methods so that everyone plays together nicely.
(All that said, I have often shared your frustration that reuse via composition has so much verbose “ceremony” around it. It would be great if we could find a way to lower the syntactic burden of composition.)
(*) I am definitely not an expert on Eiffel; that said, your idea seems to me to be like the Eiffel concept of non-conforming inheritance. Perhaps an expert on Eiffel would like to comment on this?