List<T> is a class and implements both the ICollection<T> and IEnumerable<T> interfaces. Also, ICollection<T> extends the IEnumerable<T> interface. They are not interchangeable, at least not from all points of view.
If you have a List<T>, you are guaranteed that this object implements methods and properties required to be implemented by the ICollection<T> and IEnumerable<T> interface. The compiler knows it and you are allowed to cast them "down" to either an ICollection<T> or an IEnumerable<T> implicitly. However, if you have an ICollection<T> you have to check explicitly in your code first whether it is a List<T> or something else, perhaps a Dictionary<T> (where T is a KeyValuePair) before casting it to what you desire.
You know that ICollection extends IEnumerable, so you can cast it down to an IEnumerable. But if you only have an IEnumerable, again you are not guaranteed that it is a List. It may be, but it could be something else. You should expect an invalid cast exception if you do attempt to cast a List<T> to a Dictionary<T> for example.
So they are not "interchangeable".
Also, there are a lot of generic interfaces, check out what you can find in the System.Collections.Generic namespace.
Edit: regarding your comment, there is absolutely no performance penalty if you use List<T> or one of the interfaces it implements. You will still need to create a new object, check out the following code:
List<T> list = new List<T>();
ICollection<T> myColl = list;
IEnumerable<T> myEnum = list;
list, myColl, and myEnum all point to the same object. Whether you declare it as a List or an ICollection or an IEnumerable I'm still requiring the program to create a List. I could have wrote this:
ICollection<T> myColl = new List<T>();
myColl, at runtime is still a List.
However, an this is the most important point... to reduce coupling and increase maintainability you should always declare your variables and method parameters using the lowest possible denominator possible for you, whether it is an interface or an abstract or concrete class.
Imagine that the only thing the "PerformOperation" method needs is to enumerate elements, do some work and exit, in that case you do not need the hundred more methods available in List<T>, you only need what is available in IEnumerable<T>, so the following should apply:
public void PerformOperation(IEnumerable<T> myEnumeration) { ... }
By doing that, you and other developers know that any object of a class implementing the IEnumerable<T> interface may be given to this method. It may be a List, a Dictionary, or a custom collection class another developer has wrote.
If on the contrary you specify you explicitly need a concrete List<T> (and although it is rarely the case in real life, it may still happen), you and other developers know that it must either be a List or another concrete class inheriting from List.
I'm currently thinking of using the following algorithm:
Start in the directory of the including file.
Is the header file found in the current directory or any subdirectory thereof? If so, done.
If we are at the root directory, the file doesn't seem to be present on this machine, so skip it. Otherwise move to the parent of the current directory and go to step 2.
Is this the best algorithm to use? In particular, does anyone know of any case where a different algorithm would work better?
I have been working in projects that had a setup like this:
prj
|
+-config.h
|
+-sub_A
| |
| +-config.h
| +-...
|
+-sub_B
| |
| +-config.h
| +-...
|
+-...
Those are then referred to as
#include "config.h"
#include "sub_A/config.h"
#include "sub_B/config.h"
...
Simply searching for a header with a matching file name would blow this, which I believe means that a different algorithm would work better.
Best Answer
Not quite. Definitions in a header file correspond more closely to
public
members of a Java class. Both header files and public/private access modifiers allow us to make a distinction between the publicly visible interface/API of our code, and its internal implementation details.C or C++ declarations are subject to the One Definition Rule. A compiled program can contain only one implementation for each declared function or global variable (unless it is declared as
inline
, in which all definitions MUST be equivalent). So for one function declaration in a header, I write one function definition in a .c or .cpp file. If I want to use a different definition, I need to recompile the code.Java
interface
s (or C++ classes with pure virtual methods) also declare a kind of “interface”, but it is very different: oneclass
can implement manyinterface
s, and oneinterface
can be implemented by manyclass
es. The latter point is very important: I can write code that only calls methods on an object through aninterface
. Later, I can provide objects that conform to thisinterface
to that code, and the code will still work! I can choose multiple implementations at run time, without having to recompile the code. This provides a lot of flexibility, e.g. for dependency injection.Fundamentally, headers and
interface
s work very differently. Headers contain forward declarations that are resolved later, but still during compilation. Aninterface
(or non-final
class
) defines virtual methods that are resolved at run time through dynamic dispatch.They also mean different concepts. Mechanisms like headers and public/private access modifiers in classes allow us to implement abstract data types (ADTs). ADTs encapsulate implementation details of some data structure, and only allow external code to access to those structures through public functions or methods. In contrast,
interface
s and virtual methods allow us to implement objects. Oneinterface
can be implemented by many different objects. This ability of objects of one commoninterface
to behave differently is also called polymorphism.ADTs and objects both have something to do with “encapsulation”, but they do that very differently. They should be understood as complementary concepts.
However, there has been significant interest in object-oriented solutions. E.g. the Design Patterns book investigates how applying object-oriented techniques like dynamic dispatch can introduce enough flexibility into a software system to solve some interesting problems. As a random example, the Composite Pattern allows us to treat collections of items equivalently to a single item. How? both the collection and single items implement the same
interface
. This wouldn't be possible just with header files.