Summary#
An interface is a contract: what a type must provide, with no bodies and no state.
interface IBlobStore {
string Put(string key, string body);
string Name { get; }
}
class S3Store : IBlobStore {
public string Put(string key, string body) { return "s3://" + key; }
public string Name { get; } = "s3";
}
class GcsStore : IBlobStore {
public string Put(string key, string body) { return "gs://" + key; }
public string Name { get; } = "gcs";
}A value typed as the interface accepts either, and the call runs whichever it holds:
string Upload(IBlobStore store, string key) {
return store.Put(key, "…"); // "s3://k" or "gs://k" — decided by what `store` IS
}Signature#
interface IName {
ReturnType Method(params); // an obligation to DO something
Type Property { get; } // an obligation to ANSWER something
Type Property { get; set; } // …and to accept one
}
class Name : Base, IOne, ITwo { … } // one base class, then any number of contracts
interface IBoth : IOne, ITwo { } // an interface may extend othersMembers are public without saying so, and abstract without saying so — that is what an interface is.
Description#
Interface or abstract class?#
Both let you call through a shape rather than a concrete type. Choose by what you need to share:
interface | abstract class | |
|---|---|---|
| shared behaviour (a body) | no | yes |
| shared state (a field) | no | yes |
| how many a type may have | many | one |
An interface is the right default for "these types all do X" — a store, a formatter, a notifier. Reach for an abstract class when implementors should share code, not just a shape.
Implementing one#
A type lists its contracts after its base, and must provide every member — the compiler names the ones it is missing. An inherited implementation counts:
class Logged : S3Store { } // Put/Name come from S3Store, so `Logged` is an IBlobStore tooAn abstract class may implement a contract partially and leave the rest to its subclasses.
Holding and testing one#
An interface is an ordinary type: a field, a parameter, a return, a collection element. is narrows back out of it:
string Describe(List<IBlobStore> stores) {
var s = "";
foreach (var store in stores) {
s = s + store.Name;
if (store is GcsStore) { s = s + "(google)"; }
}
return s;
}What an interface may not do#
Each of these is refused with the reason, not a parse error:
- It cannot be created.
new IBlobStore()has no body to run — create one of the implementors. - It holds no state. A field is refused; a property contract (
int Count { get; }) is how you require a value. - Its members take no body. Behaviour shared between implementors belongs on an abstract class.
- An
entityimplements none. A row lives in one table told apart by one discriminator; a contract is a compile-time promise with nothing to store. Move the behaviour to aclass.
⚠ A generic contract cannot be inherited yet — class R : IRepo<int> is refused, and so is class B : Box<int>.
That is the generic-inheritance limit, not an interface one; the compiler says so where you write it.
Examples#
Choosing an implementation at run time — the shape this exists for:
string Store(bool useGcs, string key) {
IBlobStore store = new S3Store();
if (useGcs) { store = new GcsStore(); }
return store.Name + " " + store.Put(key, "payload");
}Two contracts on one type:
interface IAudited { string Who(); }
class AuditedS3 : S3Store, IAudited {
public string Who() { return "auditor"; }
}
string Trace(IAudited a) { return a.Who(); }See also#
- Class inheritance —
class B : A,virtual/override/abstract, and when a base class is the better shape - class methods — the members an implementation is made of
- Sequence fields on a class — holding many implementors in a
List<IContract>