When I first try to demonstrate the power of abstraction to developers, often they get confused. Partly this is because they’re unused to thinking in these terms, but partly it’s because there’s so many different types. Ultimately, it’s a case of tools for the job: you pull out a hammer when you’ve got a nail, a screwdriver when you’ve got a screw. The problem is: they can all be combined together in useful ways. There’s no such thing as a hammer than you twist, but abstract classes and interfaces can be used together in the one solution.
Everyone understands parameterization. If you’ve got a routine that writes to a file, the routine is more general if it takes the destination filename as a parameter than if it hard-codes it in. What not everyone does is take it far enough: we’ve already talked out parameterizing dependencies (aka dependency injection), but even for primitive types, there’s more parameterization that can be done. A classic case of this is the class that reads a string from a config file. Just passing the string in as a parameter is more general and more flexible.
Inheritance and Abstract Classes
Abstract classes allow for generality with the re-use of code. Unless you make absolutely every method virtual (which, typically, you should) they’re not as general as interfaces. The single inheritance restriction somewhat limits their usefulness as well.
Operationally, interfaces are almost exactly like abstract classes with no code and every method declared virtual. However, one class can implement multiple interfaces, which we’ll see later helps us support the Interface Segregation Principle.
Dynamic types aren’t in C# yet, but will be soon. Here, you can call any method, but it’s resolved (or fails to resolve) at runtime. That’s equivalent to having your own custom interface you don’t need to declare. It might well be worth using code contracts to actually check that your dynamic object behaves the way you think it does.
Any old VB programmer knows this concept already: it’s late binding. He’s probably also quite wary of it. The average python or ruby programmer knows it already and is a lot more comfortable with it. Us C# programmers are going to have a lot to learn from them over the next few years.
Generics are the most powerful tool in the C# armoury, and due to become more powerful with C#4. Here, you’ve got the benefits of abstraction, but you can specialize later. So, your original base class can require a TWebRequest, which is declared to be a type that inherits from WebRequest, but you can inherit from this and then restrict TWebRequest to be an HttpWebRequest.
Generics in C# were created to support Nullable<T> and Collection<T>, but they’re much, much, more powerful than that. Any parameter can be made generic, even if it’s an interface already.
The major problem with generics right now is the type system is cussed. For instance, IEnumerable<int> can’t be case to IEnumerable<object>, despite the fact that this would cause no problems. On the other hand, List<int> will never be castable to List<object>, because that would imply that you could add a float to a List<int>.
If there’s a lesson to be learned here, it’s that you still need to think about abstraction when actually instantiating a generic. A list of an interface type is still more general than a list of abstract type. A list of abstract type is still more general than a list of a concrete type.
In my experience, people are quite uncomfortable with delegate abstraction. Coming from a maths background, the idea of parameterizing one function with another is second nature. A functional programmer won’t show many problems with the concept either.
However, there’s actually a very easy way to think of it. Imagine you had an interface that had exactly one method and passed in the interface instead. It behaves exactly the same.
There’s one other problem with delegates as dependencies: Castle Windsor doesn’t really inject them naturally.