 For the sake of people who are coming to Unity with no C-sharp or Java experience, I want to go through the basics of C-sharp as quickly as possible. And the way I'm going to do it is I'm going to start with my Pigeon educational language as a basis. I'm going to assume you understand that stuff of functions and variables and control flow, the stuff common to all programming languages. But then building on that notation, we're going to add into Pigeon the semantics of C-sharp. So we'll be learning a C-sharp-like language, at least semantically, just with different syntax. And then once we get through that, we'll translate over into the real C-sharp notation. It may sound like extra work to go this path, but I think it's actually more efficient because the syntax is clearer and will distract you from the semantic ideas. So first, looking at this Pigeon code here, I have this function foo with two parameters a and b. If a is greater than five, then we turn the string high, otherwise we return the addition of a and b. And then in main, we're creating a local over x, assigning a string to it, calling foo with integers nine and ten. And then calling foo again with x and six, but we're going to get an error in this call because the string will pass to a and then when this operation is performed, the language checks every time it does an operation, hey, do we have the right kinds of inputs? And if not, it throws an exception and you can't add together a string and an integer, so this gets us an error. So that's what we call a type error. We're attempting to perform an operation or call a function with the wrong kind of input. Pigeon checks for type errors when an operation is executed. So we say that Pigeon is a dynamically typed language. The alternative to dynamic typing is called static typing. And if Pigeon were a statically typed language, well, first off, we would denote types and code at certain places. And so here for our purposes, we'll say capital I refers to the integer type and capital STR refers to our string type. And now the same code we written in a static version of Pigeon might look something like this. Our function foo, we have to specify what type of value it's going to return. And we're going to say here that returns an integer. And then for our parameters, we also have to specify what types are. So if you say a is an integer and b is an integer, we're putting the type name after the parameter. This colon here is just for visual separation. So it's easier to distinguish the parameters from the name and the return type. So we specify the types of our functions, meaning the return type of that function and the types of the inputs. And then also on our variables, we specify what their types are. So here this variable X, we specify it's a string variable. And what that means is that when we assign to the variable, the compiler checks to make sure we're assigning the right kind of value. So this is valid. But then if I make this some other time, let's say an integer, now it's invalid. The compiler will complain about this assignment and say, I refuse to compile the code until you correct this. So let me put that back and now it's good. And then when I call foo, the compiler looks at this and says nine and 10. Yeah, that's valid. You can pass integers to foo it's expecting two integers and we're passing two integers. That's good. But now here, if we pass foo X six X, the compiler knows is a string because that's how it's declared up here. It'll know for sure this is always going to be a string. And so it knows this call is invalid. And so we get a compilation error and the compiler tells us, Hey, on this line, you have this function call where an integer was expected, but instead you're passing in the string. Lastly, having specified that foo is a function returning an integer, the compiler will force that as well. So anywhere we have a return statement inside foo here, it has to be returning an integer and not any other kind of value. So if this we're returning a string like we had up here, that's invalid, we can't return anything other than an integer from this function foo. Whereas in the dynamic version of pigeon, we didn't have that problem in a dynamic language in our functions just at any point we can return whatever we want. So down this branch of code returning a string down this branch of code returning an integer, whatever the compiler's happy. So this is a case where dynamic typing is arguably more flexible. In a static code, we have to have our function return just one type, except there is actually a workaround for this as we'll get into later. There's a way you can have a function, you declare to return one type, but the net type has so called sub types. And so you're not locked into returning just one specific kind of piece of data. So you can return different types of things from a statically typed function. There is no some extra work you have to go through to achieve that. In the dynamic language, it's couldn't be more straightforward, you just return whatever you want at any point. On the other hand, in the static type language, we now have the benefit of any time we call function foo the compiler knows for sure what it's going to return. It's guaranteed at compile time that this function foo is always going to turn an integer. So if we had, let me just add another variable of type integer, I, and now if I were to assign this to I, the compiler can know for sure that yes, this call returns an integer. And so this assignment is valid. So the some effect of annotating the types in our code by specifying what the return types of our functions are, what the types of their parameters are and what the types of our variables are, is that the compiler can analyze our code and know the type of every expression in the code. It can look at every call, every variable, every value and say, I know what that thing is, I know what type it is. It doesn't know specifically the values, of course, but it knows the types. And so we can do type checking at compile time and effectively eliminate all type errors at runtime. In a dynamic language, there's always the danger that we don't know if our code has type errors or not. Yeah, we can run the program and see if we get a type error. But of course, there can be branches of code that don't necessarily always execute they execute only under rare conditions. And so we can't really systematically say that our code doesn't have any type errors in a dynamically typed language. On the other hand, advocates of dynamic typing will say there are cases where dynamic typing is more convenient and more flexible. And they also argue that eliminating type errors at compile time is really not that big a deal. Because of course, type errors aren't the only possible kind of bug in our code. We can have just logic errors of any kind in our code. So why are type errors so special? That is the argument they might make. Another point in favor of static typing, though, is that statically typed languages generally are more efficient because the compilers can know for sure what types everything are. And when it knows what type everything is, it doesn't have to do extra type checking at runtime like we do here in regular dynamic pigeon where you do this ad operation and it has to check, are these two things integers? It doesn't know at compile time and has to defer that question until it does the operation every single time. Whereas here, the compiler knows for sure A and B are both integers. So it doesn't have to bother to check their types. So there's less work at runtime. Also by knowing what type everything is, in many cases, the compiler then knows exactly how large the thing is, meaning how much space it occupies in memory. And that opens up a lot of possibilities for optimization that the compiler can do. So generally, statically typed languages are more efficient. So in our C sharp code, anytime we create a variable, we have to specify what type it is. And anytime we create a method, which is what C sharp calls as functions, we have to specify the return type and what the types of the parameters are. As I discussed in another video about number representation, the way numbers are represented as bits, one way to do it is what are called floating point numbers where the value can be not only an integer, it can also have a fractional component, it can have basically a decimal point, like say 82.4, that is a floating point value. Alternatively, though, when we know we just need to deal with integer values, there's a more efficient representation that doesn't account for fractional values. And so generally, if we want to be efficient, we'll make the distinction between floating point numbers and integer numbers. In Pigeon and other languages where performance is not a high priority, we can simply represent all of our numbers in terms of floating point, but in languages that are trying to be more efficient, like C sharp, a distinction is made between integers and floating point. So in our C sharp code, we have integer type numbers and floating point type numbers, and they're not considered to be the same thing. They are different types, even though they're both numbers. And so here, if we can create a variable with the word var, specify its name, and then it's type, this is creating a variable i of type integer, this is creating a variable f of type float. And then we can assign floating point values like 82.4 to f. We can assign integer values like 6 to i, but then if we try and assign f to i, they're both numbers, but the compiler looks at this and says, well, you're a floating point value, not an integer value, so I can't assign you to i. However, using a cast operation where we specify the type we are casting to, the type we're converting to, we can cast our float values to integer values, and this does a conversion. It gets us the integer equivalent of the floating point value f. And of course, there isn't any exact equivalent, any mathematically exact equivalent of 82.4 expressed as an integer. This simply is not an integer value, but what the cast operation does in this case is it'll just truncate. It'll get us the integer value 82. It just hacks off everything after the decimal point. So this returns 82 as an integer value, and that is assigned to i, and that is valid. And we can go the other way around. We can assign to f the cast of to float from i, and this goes the opposite way. But of course, in this example, the cast doesn't distort the value. We get the mathematically equivalent value. We started with a net value, but we get the float equivalent, which in this case is mathematically the same value. Now, integer floats and bool are considered in C-sharp to be so-called value types, meaning that variables of this type store the values directly. So this float variable here, when we assign at the value 82.4, the bytes that make up this value are copied to be stored directly in f. Same with i here. You assign six to it, and the bytes that make up this value are copied to be stored directly in i. Whereas with reference types like string, a string variable itself does not directly store string. Instead, it stores just a reference. It stores the address. So here, the string high is created somewhere in memory. We don't care exactly. The language decides where to put it. And then what's being stored in s is the address of that string, wherever it is in memory. So for reference types, there's a special value null, which is equivalent of what I'm pigeoning we call nil. It's just the value representing nothing. It's the reference pointing to nothing. And so you can assign null to s. You can have a string variable, a reference variable that points to nothing. That's valid. But you can't assign null to an integer variable, or a float variable, or any value type variable. So this isn't valid, whereas this is okay. So again, value types are stored in their variables directly, whereas reference types are not. And this distinction doesn't have any consequence here in this example, but it'll have significance later on. In pigeon, our code consists primarily of functions and global variables. We do have functions and global variables in C sharp, but they go by different names, and they're contained within these things called classes. So what is a class? Well, a class is, in a sense, a data type. We are defining here a data type called cat, and we'll say in our CS pigeon language that all type names, including our class names, have to begin with a capital letter, whereas other things cannot begin with capital letters. So it's capital C cat. And this data type cat is composed of three data elements. These data elements we call fields, and the first here has the name name, confusing I know, but the name happens to be name, and its type is string. The second field has the name age, and the type integer, and the third field has the name weight, and the type float. And so having defined this data type, we can create values of this type, and each value of that type will be composed of these three elements, these three fields, a name and age and a weight, a name of type string and age of type integer, and a weight of type float. There's no real sense of order amongst these fields. If I wrote them in a different order, it would still logically be the same class. The compiler actually doesn't care what order we write these things in. So having defined this class cat, we can then create a variable of type cat, we'll call it C, and understand that classes are reference types. And so what this variable C will store is not any cat directly, it stores the address of a cat. When we create cats, they exist somewhere in memory, and then we assign them to C. C then stores a copy of the address of the cat, not the cat itself directly. To actually create the cat values, or as what we often call instances and objects of the class cat, I'll probably use the term instance most frequently. To create a cat instance, we use the new operator, we specify the name of the class, and this returns a newly created cat instance, whose address now is going to be stored in the variable C. Upon creation, the fields of this new instance, the name field of string that defaults to null, and age and weight will both default to zero. But if we want to change that, we can use the set operator to specify the instance whose field we are setting. We specify the name of the field, in this case name, and we specify its value in this case of string mittens. So again, strings are not really stored directly in variables or fields. String is a reference type, and variables and fields don't store reference types directly, they store their addresses. So actually now what's happening is the address of the string mittens is copied to the name field of this instance of cat. And then here we set the age of cat C to five. But because this is a value type, the value five is stored directly in the field age. To retrieve the values of the fields, we use the get operator, specify the instance, and the name of the field, in this case the field called name. And so this is retrieving the string referenced by name, though more accurately it's getting us the address of that string, and that is then being copied to this variable S of type string. So now the variable S and the name field of C are both referencing the same string. The string exists in one place in memory, and both this name field and this variable S store the address of that string. Now here I'm creating a second cat variable called C2, and initializing this with the value of C. So C2 and C both now are referencing the same cat instance. There's only one cat instance so far we've created in memory, but C and C2 both store the address of that very same cat instance. And so now if I set the age of C2 to six, if I read the age of C, both C and C2 are just references pointing to the same cat. So here having set that instance's age to six, when I get the age, even though it's through a different reference, it's a reference pointed to the same instance, and so this returns six. If though we create another new cat, we create a separate cat and assign that C2, now C2 is referencing a different cat. If we set the age of C2 to seven and then access age of C again, well it's still six because when we set the age here, we're setting the age of the cat created on this line, not the one created up here. I understand that having defined a class, we can then use that class as a data type just like we use any of the built-in data types. So here for example if we have this class door made up of a color, which is a string and a height, which is a floating point value, well then if we also have a class house, it tends to have a field of type door and these other classes, like say if we had a class kitchen or garage, this house then would be composed of references pointing to instances of those other types. We call this composition. A data type we can define can be composed of elements of other types we've defined. In fact, perhaps surprisingly, a class can have a field of its own type. Like say here, this class cat, we have a field mother of type cat, and so when we create a new cat here, we set its mother to, in this case, a new cat, a separate cat, and that makes sense, except for the part where the mother was created after the child, but this is code, not reality. And in fact, we can do something even stranger. We can set the mother of our cat to be itself, which sounds logically impossible and of course in reality is, but in code, this value here is just a reference pointing to some cat somewhere in memory. So once we've already created the cat and stored in reference C, we can then just copy that address to this field. We can then have a recursive piece of data that has a reference to itself.