Learning C# by Developing Games with Unity 2020
上QQ阅读APP看书,第一时间看更新

Working with types

Assigning a specific type to a variable is an important choice, one that trickles down into every interaction a variable has over its entire lifespan. Since C# is what's called a strongly-typed or type-safe language, every variable has to have a data type without exception. This means that there are specific rules when it comes to performing operations with certain types, and regulations when converting a given variable type into another. 

Common built-in types

All data types in C# trickle down (or derive, in programmatic terms) from a common ancestor: System.Object. This hierarchy, called the Common Type System (CTS), means that different types have a lot of shared functionality. The following table lays out some of the most common data type options and the values they store:

In addition to specifying the kind of value a variable can store, types contain added information about themselves, including the following:

  • Required storage space
  • Minimum and maximum values
  • Allowed operations
  • Location in memory
  • Accessible methods 
  • Base (derived) type

If this seems overwhelming, take a deep breath. Working with all of the types C# offers is a perfect example of using documentation over memorization. Pretty soon, using even the most complex custom types will feel like second nature.

You can find a complete list of all of the C# built-in types and their specifications at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/index.

Before the list of types becomes a sticking point, it's best to experiment with them. After all, the best way to learn something new is to use it, break it, and then learn to fix it.

Time for action – playing with different types

Go ahead and open up LearningCurve and add a new variable for each type in the preceding chart from the Common built-in types section. The names and values you use are up to you; just make sure they're marked as public so we can see them in the Inspector window. If you need inspiration, take a look at my code, which is shown in the following screenshot:

When dealing with string types, the actual text value needs to be inside a pair of double quotes, while float values need to end with a lowercase f, as you can see with pi and firstName.

All our different variable types are now visible. Take note of the bool variable that Unity displays as a checkbox (true is checked and false is unchecked):

Before we move on to conversions, we need to touch on a common and powerful application of the string data type; namely, the creation of strings that have variables interspersed at will.

Time for action – creating interpolated strings

While number types behave as you'd expect from grade school math, strings are a different story. It's possible to insert variables and literal values directly into text by starting with a $ character, which is called string interpolation. The interpolated values are added inside curly brackets, just like using the LogFormat() method. Let's create a simple interpolated string of our own inside LearningCurve to see this in action:

Print out the interpolated string inside the Start() method directly after ComputeAge() is called:

Thanks to the curly brackets, the value of firstName is treated as a value and is printed out inside the interpolated string:

It's also possible to create interpolated strings using the + operator, which we'll get to right after we talk about type conversions.

Type conversions

We've already seen that variables can only hold values of their declared types, but there will be situations where you'll need to combine variables of different types. In programming terminology, these are called conversions, and they come in two main flavors:

  • Implicit conversions take place automatically, usually when a smaller value will fit into another variable type without any rounding. For example, any integer can be implicitly converted into a double or float without additional code:
float implicitConversion = 3;
  • Explicit conversions are needed when there is a risk of losing a variable's information during the conversion. For example, if we wanted to convert a double into an int, we would have to explicitly cast (convert) it by adding the destination type in parentheses before the value we want to convert. This tells the compiler that we are aware that data (or precision) might be lost. 

In this explicit conversion, 3.14 would be rounded down to 3, losing the decimal values:

int explicitConversion = (int)3.14;

C# provides built-in methods for explicitly converting values to common types. For example, any type can be converted into a string value with the ToString() method, while the Convert class can handle more complicated conversions. You can find more info about these features under the Methods section at https://docs.microsoft.com/en-us/dotnet/api/system.convert?view=netframework-4.7.2

So far, we've learned that types have rules regarding their interactions, operations, and conversion, but how do we handle a situation where we need to store a variable of an unknown type? This might sound crazy, but think about a data-download scenario – you know the information is coming into your game, but you're not sure what form it will take. We'll discuss how to handle this in the following section.

Inferred declarations

Luckily, C# can infer a variable's type from its assigned value. For example, the var keyword can let the program know that the type of the data, currentAge, needs to be determined by its value of 32, which is an integer:

var currentAge = 32;

While this is handy in certain situations, don't be suckered into the lazy programming habit of using inferred variable declarations for everything. This adds a lot of guesswork to your code, where it should be crystal clear.

Before we wrap up our discussion on data types and conversion, we do need to briefly touch on the idea of creating custom types, which we'll do next.

Custom types

When we're talking about data types, it's important to understand early on that numbers and words (referred to as literal values) are not the only kinds of values a variable can store. For instance, a class, struct, or enumeration can be stored as variables. We will introduce these topics in Chapter 5, Working with Classes and OOP, and explore them in greater detail in Chapter 10, Revisiting Types, Methods, and Classes.

Types roundup

Types are complicated, and the only way to get comfortable with them is by using them. However, here are some important things to keep in mind:

  • All variables need to have a specified type (be it explicit or inferred).
  • Variables can only hold values of their assigned type (string can't be assigned to int).
  • Each type has a set of operations that it can and can't apply (bool can't be subtracted from another value).
  • If a variable needs to be assigned or combined with a variable of a different type, a conversion needs to take place (either implicit or explicit).
  • The C# compiler can infer a variable's type from its value using the var keyword, but should only be used when the type isn't known when it's created.

That's a lot of nitty-gritty detail we've just jammed into a few sections, but we're not done yet. We still need to understand how naming conventions work in C#, as well as where the variables live in our scripts.