 In addition to classes, in our C sharp code, we can create what are called interfaces. An interface is a list of method signatures. What is a method signature? Well, it's the part of a signature which the caller needs to know to call the method. It's the name of the method, plus the return type, plus the types of the parameters. If I'm calling a method, that's what I need to know. I don't need to know what the names of the parameters are. I don't need to know what the body of code is. I just need to know the name, the return type, and the input types. So here, for example, is an interface called IAnimal. And this is a convention in C sharp, as it all interfaces begin with a capital letter I. And this interface has two method signatures. A method sleep, returning a float, and taking a float, and a method eat. Returning nothing and taking nothing. Having defined this interface then, any class which has methods matching these signatures, any class with a method sleep, returning a float, and taking a float, and a method eat, taking nothing, returning nothing. Any class with a complete set of methods matching those signatures can implement the interface. And so here, for example, if we have class cat, and we have class dog, and they both have methods eat, taking nothing, returning nothing, and method sleep, taking a float value, and returning a float. Let's say the input value f represents time, the amount of time to sleep, and then the cat sleeps. We don't really care what the methods do, so we'll just say that it prints out cat sleeping, and it prints out cat 18, and dog does the same, but it says dog instead of cat. So it doesn't matter really as far as language is concerned for interfaces. It doesn't matter what these methods do. They can all do whatever. This method sleep could launch missiles. This method sleep and dog could do something entirely different. The language doesn't care. But because these classes have methods matching all of the signatures in this iAnimal interface, we can mark both these classes as implementing the interface. And we do so in C-sharp with a colon, followed by a list of all the interfaces which this class implements. In this case, they just implement the one interface, iAnimal. So now the compiler knows that both cat and dog implement the iAnimal interface. You might think, well, the compiler could figure out for itself which classes implement an interface. They could just look at all the methods and see which has a complete set. But C-sharp doesn't do that. It could. There's a language go where that's how interfaces work. The language just figures out which types implement which interfaces. But we have to explicitly in C-sharp denote it on our classes which interfaces they implement. And in fact, if we get this wrong, if eat has a different signature, like hey, now it takes an integer, well, now it doesn't match the signature. And now the compiler will look here and say, wait a minute, this class cat can't implement iAnimal because it doesn't have the right method signature for eat. But let's say it does have the right method signature. And now what does this mean? What is the significance? Well, here, let me bring this down. Okay, so now having defined interface iAnimal, we can use iAnimal as a variable type, including also as a parameter type in our methods, and also as a return type of our methods. We can say that a function returns a value of type iAnimal. Well, what does it mean to be a value of type iAnimal? Because we can't instantiate iAnimal itself. There's no such thing as a value concretely of iAnimal itself. But any class which implements iAnimal is considered by the compiler to be a subtype of iAnimal, meaning that instances of those types are considered to be instances of iAnimal. And so I can assign to A, I can assign it instances of cat, or I can assign it instances of dog, or instances of any other implementer of iAnimal. We can have any number of classes implementing this one interface. Anyway, having assigned cat to A, be clear, interfaces like classes are considered to be reference types. So variables of these interface types really just store references, not the values themselves. So the address of cat is now stored in A. But the compiler doesn't consider A to be a cat, it just considers A to be an animal. It doesn't know specifically what kind of animal. It doesn't know if it's a dog or a cat. For the very same reason, it doesn't presume to know what the value of any variable is. It doesn't know if this value is null or what. Because there are many cases where it couldn't figure it out because of runtime logic. And so the compiler never presumes to know anything about what A is other than it's of type animal. Could be a dog, could be a cat, or some other implementer of animal. So then all we can do with A is call the methods of the interface. Because it's guaranteed effectively that at compile time, the compiler can know whatever A is, it has to be an implementer of animal. And so we should be able then to call methods of the interface. We can call here sleep, A, pass in a float value. And at compile time, the compiler doesn't know specifically which method is being called. It just knows it's this sleep method with this signature of one of the implementers of animal. But it doesn't know if it's dog sleep or cat sleep. It doesn't know which one of those two. And so it defers the question until runtime. Not until this is actually executed, does C sharp look and see, hey, what is A actually storing? Is it actually dog or is it a cat? In this case, it's going to be a cat. And so at runtime here, when this is executed, the compiler will decide, okay, this is calling sleep of cat. Here too for all of our method calls, the choice of which method of which class and which overload within that class was entirely a compile time choice. But now it's only partially determined to compile time, which method's being called and the choice of which specific method to call is deferred until runtime. This is runtime polymorphism, as we call it. And this could be useful because imagine say we have some method that takes in an input and we don't want to specify specifically, oh, it has to be a cat or a dog. We want to be more general than that. We want to require that whatever gets passed in and has to be something which we can perform certain operations with. Well, that effectively is what an interface defines an interface defines a set of methods which a set of classes all have in common. And so we can define a method that takes and say just any animal, whether it's a dog, a cat or other animal types we define all implementing this interface. And we don't have to care specifically what kind of animal. It's then up to the implementers of these classes to decide, well, cats and dogs, they're both things that sleep, but maybe they sleep in their own particular way. So it makes sense that they can have their own separate sleep methods, both cats and dogs eat, but maybe they eat in their own particular way. So again, be clear, there's no necessary relationship between the code in this eat method and the code in this eat method. They can do entirely different things. Generally, the intention behind interfaces is that, well, the language could just enforce what the method signatures are, but generally you have an idea some semantics of what the method is supposed to do. The language can enforce that part, but that's left up to you the implementer of an interface. You have to abide by the informal contract of what the interface methods are supposed to do. But again, that's up to you, not the language. Now be very clear here for our variable A, its type as far as the compiler is concerned is animal, ianimal. And so even though we can look at this code and say, yes, of course, this is going to be a cat, I should then be able to access the fields of the cat instance through this reference A. But no, the compiler will not allow this because as far as the compiler knows, A is an animal, but not any specific kind of animal. The compiler doesn't presume to know which specific kind of animal this is. In this case, you could very trivially do the logic and figure out, yeah, of course it's going to be a cat, but again, there are going to be trivial cases like say if and then some condition and then you go down to one branch and it's a cat, but you go down another branch and oh wait, now this time it's a dog. And now it's simply impossible a compile time to figure out whether this here is going to be a dog or a cat, it's just going to change depending upon what happens in the code at runtime. So the compiler never presumes to know and so it never lets us use this thing as a specific concrete type. It only lets us use it as the interface type. We can only just call the methods of that interface. Of course though, there are cases where given a value of an interface type, we want to get the actual instance as its own type. We want the compiler to let us do stuff with that instance as its concrete type. And we can achieve that with the cast operator. So again, we have A of type by animal based on some condition. It's either going to be a dog or a cat and we don't know. But what we can do is a cast operation and we can cast our reference A known by the compiler to be an animal to cat and this operation returns the same instance referenced by A, and the current type of this expression is cat. So the compiler is happy with this assignment. It says, oh, you're assigning a cat to a cat variable, that's okay. Again, the reference A which we're casting and the reference returned are both references to the same instance. It's the same address, but to the compiler they have different types. The reference A has type I animal. The reference returned by the cast has type cat. And because A might not actually be a cat, it could be a dog or something else. It's a parameter of I animal. At runtime, this cast operation performs a check. It looks at the type and says, well, are you actually a cat? If so, everything's fine. But if not, if this is not a cat, it throws an exception. And because we generally don't want to trigger exceptions in our code, there are exceptions to that, but generally we don't. You really should only use this kind of cast when you know for certain that the thing you're casting to cat is actually a cat. If you don't know for certain, if you just think, well, it might be a cat, then you want to use cast as, which instead of throwing an exception, when A is not a cat, it will just return null. So what we always want to do next is check if what we got back is null or not. So we check if it is not equal to null. Well, then yeah, we got a valid cat and we can do whatever with that cat. Otherwise, well, it wasn't a cat, we need to do something else. Last thing about interfaces. I mentioned earlier there's the problem in a statically typed language that sometimes you want to return different kinds of values in a single method or function. Well, interfaces provide us one means to do that because an interface is a type like any other. So when we define our method, we can say that returns an interface type. And then in the code, say we branch on some condition, unspecified here, but some condition and down one branch, we can return one implementer of the interface, in this case dog, but then down other branches, we can return other implementers of the interface, like say cat here. So because interfaces have subtypes, when a method returns an interface type, that method can effectively return different concrete types. One more major concept in C sharp is what's called class inheritance. Here's an example where we have a class reptile, and we have a class snake. And the colon here with a class name after this denotes that snake is inheriting from reptile. After the colon, when you list interface names, that means the class is implementing those interfaces. But the first thing after the colon, if it's a class, that means that this class snake is inheriting from that class. So snake inherits from reptile, and what that means is that everything reptile has, snake has as well. So inside snake, you only see one field here, the length field of type float, but it also implicitly has a field name of type string and a field age of type integer. Snake, the child class is inheriting that stuff from its parent class reptile. The child class also inherits all of the methods from its parent, including eat and lounge. So snake has those methods as well, except in the case of eat, snake has overridden the method, meaning basically it has redefined it. So yes, snake inherits this method eat from reptile, but it has overridden it with its own version of the method. Same method signature, different code. So now, say we're in some method somewhere in code, and we create a snake variable s and assign it a new snake instance. And then, well, we can set its name because even though the snake class itself doesn't say it has such a field called name, it actually does because it's inheriting that field from the reptile class as parent. Every snake instance has not just a length, but also a name and an age. If we then create a variable of type reptile, well, of course, we could assign instances of reptile to this variable r, but we can also assign it instances of snake because a parent-child relationship between classes is effectively a subtype relationship. The parent class here reptile is the supertype, and snake, the child class, is a subtype of reptile. And so, as far as the compiler is concerned, anywhere we're supposed to have a reptile, well, a snake would suffice. Instances of snake are considered valid instances of reptile, which makes sense because everything a reptile has, a snake is going to have that too. A snake is everything a reptile is, it just has potentially more stuff as well. This doesn't work the other way, however. The compiler doesn't consider reptiles to be snakes, and so we can't assign r here to s. That's invalid. Even though in this particular case, you and I can both look and say, well, this reference r is actually holding a snake. The compiler will still disallow this because it doesn't presume to know what actual value r is holding. It doesn't know that in this case it is holding a snake. It could be an instance of the reptile class itself or an instance of some other subtype of reptile. If, though, we really want to take the snake instance to be referenced by r and get a hold of it as a snake so that the compiler lets us access the stuff, particularly a snake, such as a length field, well, we can do that if we cast. So here I'm going to cast this reptile to snake, and if this reference r here is not actually referencing the snake at runtime when this cast operation is performed, we get an exception. Otherwise, it just returns the very same instance, referenced by r, except now the compiler considers the result of this operation to be a snake. This operation is a snake expression, and so this assignment is now valid. We can assign a snake to a snake variable s. So when we get rid of this compile error, so now this is okay. So now both r and s are referencing the same instance of snake, and given these references, well, we can set the length via s to be 4.2, and that's okay, but if we try and set the length via r, SNR are referencing the same instance, but again, the compiler considers this to be a reptile. The compiler looks at this and says, well, yeah, it might be a snake, but it doesn't presume to know that, as far as it knows it's just a reptile, so it won't let us set the length property, which reptiles don't necessarily have. Snakes are reptiles, and they have length properties, but not all reptiles have length. So you'll get a compile error here. Now, what about the methods and this override business? Not to be confused with overloading, which is a different thing. Well, say here we have our variable r of type reptile, which is going to happen at runtime. We're either going to sign our new reptile or a new snake. So when we get down here and we call the eat method, at compile time, we don't know if r is referencing an instance of reptile or snake. It could be either. At runtime, then, when this method is called, then C sharp looks and says, well, what is the actual type of r? If it's a reptile, then we call this version of the eat method. If it's, though, a snake, then it's this one that's called. It's whichever is most specific to the type. If snake did not override the eat method, then snake would just be inheriting this version of eat. And so either way, this call here would call this method the one defined in reptile. But because snake has overridden eat, it's now a question at runtime of what the actual concrete type is. So interfaces were one way to introduce runtime polymorphism, a runtime choice of we're calling either this method or that method. And now with overriding inherited methods, this is the other way in which the decision of which method to call gets deferred until runtime. The general rule is that at runtime, the method most specific to that type is the one that executes. In a more complex scenario, we might have multiple levels of inheritance. We could have a class A, which is a parent of B, and B is the parent of C, C is the parent of D, and D is the parent of E. So we have this level of hierarchy, five levels deep, and we say that for a class, all the subtypes thereof, all the classes down the inheritance hierarchy, those are its descendants, and then for all the classes up the inheritance hierarchy, those are its ancestors. B, for example, has ancestor A and descendants C, D, and E. And a class effectively gets everything from all of its ancestors. B gets everything from A, but then C gets everything from B, including the stuff B inherits from A. So C is getting everything from A and B. Anyway, so imagine then in this scenario, class A has a method foo, which B then in turn inherits, and C normally would as well, except C, let's say, overrides foo, such that D and E are inheriting this override of foo rather than the one from A. So then, say we have some variable X of type A. What can we assign to A? Well, of course, we can assign instances of A. And B, its child, we can assign instances of B. But in fact, we can assign instances of any of its descendant classes. So we can assign instances of any of these classes to this variable X because this type is A. All of those things are considered to be instances of A. If we then have a call to foo X, the compiler can't know which method of foo is being called. It's either this one or this one, but the compiler can't decide between them. That has to be deferred until runtime. And then at runtime, based on whatever happened here, whatever got assigned to X, whichever concrete type, well, if the type is either A or B, then it's going to be this method foo that's called. But if the type is C, D, or E, then it's going to be this override foo. Again, the general rule is that the method called is the one which is most specific to that type. So for example, an instance D, the foo which is most specific to it is this override from C, not the one from A. Another way to think of it is that the language simply at runtime looks at the actual concrete type here, sees if it has a method that matches. If not, it looks up the chain. And the method which executes is the first one up the chain. So the idea with inheritance is that, given a data type, a defined class, we can create more specific variations of that class, which add on to what they inherit from their ancestor, and moreover, for methods, they can actually override the code, because a more specific type may need to do something in its own particular way. The ancestor class, the parent class, represents the general case, the child class, the descendant, may need to do something more specific in its own way. Thank you.