In C#, interfaces are a special "kind" of type that differs from a class in a few key ways:
- You cannot include any implementation code of their methods.
- A single class can "inherit from" (called "implementing") as many as it wants.
- You cannot instantiate a
new
instance of an interface.
(There is also some language features associated specifically with interfaces, such as explicit implementations, but those aren't important for this discussion.)
Beyond that, interfaces can be use pretty much anywhere you can use any other reference type. That includes defining fields, properties, or local variables with interface types, or using them as parameter types or return types on methods.
The trick is, if you define a property as, say, an IEnumerable<int>
, and you want to set it's value, you cannot do this:
public IEnumerable<int> Numbers { get; set; }
...
this.Numbers = new IEnumerable<int>();
That's an error. You can't create a new instance of an interface, because it's just a "template" -- there's nothing "behind" it to actually do anything. However, you can do this:
public IEnumerable<int> Numbers { get; set; }
...
this.Numbers = new List<int>();
Because List<T>
implements IEnumerable<T>
, the compiler will automatically do the type conversion to make the assignment work. Any concrete class that implements IEnumerable<>
can be assigned to a property of type IEnumerable<>
, which is why you see interface property types so often. It allows you to change the underlying concrete type (maybe you want to change List<T>
to ObservableCollection<T>
, but the users of your class neither know nor care when you do.
The same goes for methods with an interface return type, except there's an extra option here that C# throws in as a bonus:
public IEnumerable<string> GetName()
{
// this fails.
return new IEnumerable<string>();
// this works.
return new List<String>();
// this also works because magic!~
yield return "hello";
yield return "there";
yield return "!";
}
That last case is a special form of "syntactic sugar" that C# provides because it's such a common requirement. As other people have mentioned, the compiler specifically looks for yield return
statements on methods that return IEnumerable
or IEnumerator
(both generic and non-generic versions) and does some hefty rewriting of the code.
Behind the scenes, C# is creating a hidden class, which implements IEnumerable<string>
, and implementing it's GetEnumerator
method to return an IEnumerator<string>
object that provides those three string values. That would be a lot of boilerplate code for you to write, although you could certainly write it yourself. In previous versions of C#, there was no yield
and you did have to write it yourself.
If you really want to know, you can find the C# equivalent here, among other places. In essence, it takes the method that contains your yield
statements and creates a IEnumerator<>.MoveNext
method out of it, but turning it into a state machine. It uses the equivalent of labels and goto statements to jump back to the correct place each time a consumer calls MoveNext
on the same instance. Also, as I understand it, it does things that you can't actually do in C# (it jumps into and out of loops) but that are legal in IL code, so it's implementation is more efficient that what you could write yourself.
But once you get past the secret cause of the yield
keyword, you're still doing the same thing. You're still creating a class, that implements IEnumerable<>
, and using that as the return value for your method.