Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#: How to forbid implicit cast from child to parent class (i.e. inheritance as composition vs. is-kind-of)?

Tags:

c#

.net

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.

like image 706
Branko Dimitrijevic Avatar asked Jul 15 '11 16:07

Branko Dimitrijevic


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr. Stroustroupe.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

How old is the letter C?

The letter c was applied by French orthographists in the 12th century to represent the sound ts in English, and this sound developed into the simpler sibilant s.


2 Answers

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?

like image 171
Eric Lippert Avatar answered Oct 05 '22 07:10

Eric Lippert


How about:

class BaseKey
{
    // all functionality here
}

class ParentKey : BaseKey
{}

class ChildKey : BaseKey
{}

?

like image 27
Mike Mozhaev Avatar answered Oct 05 '22 09:10

Mike Mozhaev