Class and Object Basics
- Classes vs. Objects
- A class is a specification, blueprint, or template for an object; it is the code that describes what data the object stores and what it can do
- An object is a single instance of a class, created using its “template.” It is executing code, with specific values stored in each variable
- To instantiate an object is to create a new object from a class
- Object design basics
- Objects have attributes: data stored in the object. This data is different in each instance, although the type of data is defined in the class.
- Objects have methods: functions that use or modify the object’s data. The code for these functions is defined in the class, but it is executed on (and modifies) a specific object
- Encapsulation: An important principle in class/object design
- Attribute data is stored in instance variables, a special kind of variable
- Called “instance” because each instance, i.e. object, has its own copy of them
- Encapsulation means instance variables (attributes) are “hidden”
inside an object: other code cannot access them directly
- Only the object’s own methods can access the instance variables
- Other code must “ask permission” from the object in order to read or write the variables
Writing Our First Class
- Designing the class
- Our first class will be used to represent rectangles; each instance (object) will represent one rectangle
- Attributes of a rectangle:
- Length
- Width
- Methods that will use the rectangle’s attributes
- Get length
- Get width
- Set length
- Set width
- Compute the rectangle’s area
- Note that the first four are a specific type of method called “getters” and “setters” because they allow other code to read (get) or write (set) the rectangle’s instance variables while respecting encapsulation
The Rectangle class:
Let’s look at each part of this code in order.
- Attributes
- Each attribute (length and width) is stored in an instance variable
- Instance variables are declared similarly to “regular” variables, but with one additional feature: the access modifier
- Syntax:
[access modifier] [type] [variable name]
- The access modifier can have several values, the most common of
which are
public
andprivate
. (There are other access modifiers, such asprotected
andinternal
, but in this class we will only be usingpublic
andprivate
). - An access modifier of
private
is what enforces encapsulation: when you use this access modifier, it means the instance variable cannot be accessed by any code outside theRectangle
class - The C# compiler will give you an error if you write code that
attempts to use a
private
instance variable anywhere other than a method of that variable’s class
- SetLength method, an example of a “setter” method
- This method will allow code outside the
Rectangle
class to modify aRectangle
object’s “length” attribute - Note that the header of this method has an access modifier, just like the instance variable
- In this case the access modifier is
public
because we want to allow other code to call theSetLength
method - Syntax of a method declaration:
[access modifier] [return type] [method name]([parameters])
- This method has one parameter, named
lengthParameter
, whose type isint
. This means the method must be called with one argument that isint
type.- Similar to how
Console.WriteLine
must be called with one argument that isstring
type — theConsole.WriteLine
declaration has one parameter that isstring
type. - Note that it is declared just like a variable, with a type and a name
- Similar to how
- A parameter works like a variable: it has a type and a value, and you can use it in expressions and assignment
- When you call a method with a particular argument, like 15, the parameter is assigned this value, so within the method’s code you can assume the parameter value is “the argument to this method”
- The body of the
SetLength
method has one statement, which assigns the instance variablelength
to the value contained in the parameterlengthParameter
. In other words, whatever argumentSetLength
is called with will get assigned tolength
- This is why it is called a “setter”:
SetLength(15)
will setlength
to 15.
- This method will allow code outside the
- GetLength method, an example of a “getter” method
- This method will allow code outside the
Rectangle
class to read the current value of aRectangle
object’s “length” attribute - The return type of this method is
int
, which means that the value it returns to the calling code is anint
value - Recall that
Console.ReadLine()
returns astring
value to the caller, which is why you can writestring userInput = Console.ReadLine()
. TheGetLength
method will do the same thing, only with anint
instead of astring
- This method has no parameters, so you do not provide any arguments when calling it. “Getter” methods never have parameters, since their purpose is to “get” (read) a value, not change anything
- The body of
GetLength
has one statement, which uses a new keyword:return
. This keyword declares what will be returned by the method, i.e. what particular value will be given to the caller to use in an expression. - In a “getter” method, the value we return is the instance variable
that corresponds to the attribute named in the method.
GetLength
returns thelength
instance variable.
- This method will allow code outside the
- SetWidth method
- This is another “setter” method, so it looks very similar to
SetLength
- It takes one parameter (
widthParameter
) and assigns it to thewidth
instance variable - Note that the return type of both setters is
void
. The return typevoid
means “this method does not return a value.”Console.WriteLine
is an example of avoid
method we’ve used already. - Since the return type is
void
, there is noreturn
statement in this method
- This is another “setter” method, so it looks very similar to
- GetWidth method
- This is the “getter” method for the width attribute
- It looks very similar to
GetLength
, except the instance variable in thereturn
statement iswidth
rather thanlength
- The ComputeArea method
- This is not a getter or setter: its goal is not to read or write a single instance variable
- The goal of this method is to compute and return the rectangle’s area
- Since the area of the rectangle will be an
int
(it is the product of twoint
s), we declare the return type of the method to beint
- This method has no parameters, because it does not need any arguments. Its only “input” is the instance variables, and it will always do the same thing every time you call it.
- The body of the method has a
return
statement with an expression, rather than a single variable - When you write
return [expression]
, the expression will be evaluated first, then the resulting value will be used by thereturn
command - In this case, the expression
length * width
will be evaluated, which computes the area of the rectangle. Since bothlength
andwidth
areint
s, theint
version of the*
operator executes, and it produces anint
result. This resultingint
is what the method returns.
Using Our Class
- We’ve written a class, but it does not do anything yet
- The class is a blueprint for an object, not an object
- To make it “do something” (i.e. execute some methods), we need to instantiate an object using this class
- The code that does this should be in a separate file (e.g. Program.cs), not in Rectangle.cs
- Here is a program that uses our
Rectangle
class:
- Instantiating an object
- The first line of code creates a
Rectangle
object - The left side of the
=
sign is a variable declaration — it declares a variable of typeRectangle
- Classes we write become new data types in C#
- The right side of the
=
sign assigns this variable a value: aRectangle
object - We instantiate an object by writing the keyword
new
followed by the name of the class (syntax:new [class name]()
). The empty parentheses are required, but we will explain why later. - This statement is really an initialization statement: It declares and assigns a variable in one line
- The value of the
myRectangle
variable is theRectangle
object that was created bynew Rectangle()
- The first line of code creates a
- Calling setters on the object
- The next two lines of code call the
SetLength
andSetWidth
methods on the object - Syntax:
[object name].[method name]([argument])
. Note the “dot operator” between the variable name and the method name. SetLength
is called with an argument of 12, solengthParameter
gets the value 12, and the rectangle’slength
instance variable is then assigned this value- Similarly,
SetWidth
is called with an argument of 3, so the rectangle’swidth
instance variable is assigned the value 3
- The next two lines of code call the
- Calling ComputeArea
- The next line calls the
ComputeArea
method and assigns its result to a new variable namedarea
- The syntax is the same as the other method calls
- Since this method has a return value, we need to do something with the return value — we assign it to a variable
- Similar to how you must do something with the result (return value)
of
Console.ReadLine()
, i.e.string userInput = Console.ReadLine()
- The next line calls the
- Calling getters on the object
- The last line of code displays some information about the rectangle object using string interpolation
- One part of the string interpolation is the
area
variable, which we’ve seen before - The other interpolated values are
myRectangle.GetLength()
andmyRectangle.GetWidth()
- Looking at the first one: this will call the
GetLength
method, which has a return value that is anint
. Instead of storing the return value in anint
variable, we put it in the string interpolation brackets, which means it will be converted to a string usingToString
. This means the rectangle’s length will be inserted into the string and displayed on the screen
Flow of Control with Objects
-
Consider what happens when you have multiple objects in the same program, like this:
- First, we declare a variable of type
Rectangle
- Then we assign
rect1
a value, a newRectangle
object that we instantiate - We call the
SetLength
andSetWidth
methods usingrect1
, and theRectangle
object thatrect1
refers to gets itslength
andwidth
instance variables set to 12 and 3 - Then we create another
Rectangle
object and assign it to the variablerect2
. This object has its own copy of thelength
andwidth
instance variables, not 12 and 3 - We call the
SetLength
andSetWidth
methods again, usingrect2
on the left side of the dot instead ofrect1
. This means theRectangle
object thatrect2
refers to gets its instance variables set to 7 and 15, while the otherRectangle
remains unmodified
- First, we declare a variable of type
-
The same method code can modify different objects at different times
- Calling a method transfers control from the current line of code (i.e. in Program.cs) to the method code within the class (Rectangle.cs)
- The method code is always the same, but the specific object that gets modified can be different each time
- The variable on the left side of the dot operator determines which object gets modified
- In
rect1.SetLength(12)
,rect1
is the calling object, soSetLength
will modifyrect1
SetLength
begins executing withlengthParameter
equal to 12- The instance variable
length
inlength = lengthParameter
refers torect1
’s length
- In
rect2.SetLength(7)
,rect2
is the calling object, soSetLength
will modifyrect2
SetLength
begins executing withlengthParameter
equal to 7- The instance variable
length
inlength = lengthParameter
refers torect2
’s length
Accessing object members
-
The “dot operator” that we use to call methods is technically the member access operator
-
A member of an object is either a method or an instance variable
-
When we write
objectName.methodName()
, e.g.rect1.SetLength(12)
, we are using the dot operator to access the “SetLength” member ofrect1
, which is a method; this means we want to call (execute) theSetLength
method ofrect1
-
We can also use the dot operator to access instance variables, although we usually do not do that because of encapsulation
-
If we wrote the
Rectangle
class like this:Then we could write a
Main
method that uses the dot operator to access thelength
andwidth
instance variables, like this:But this code violates encapsulation, so we will not do this.
Method calls in more detail
-
Now that we know about the member access operator, we can explain how method calls work a little better
-
When we write
rect1.SetLength(12)
, theSetLength
method is executed withrect1
as the calling object — we are accessing theSetLength
member ofrect1
in particular (even though every Rectangle has the sameSetLength
method) -
This means that when the code in
SetLength
uses an instance variable, i.e.length
, it will automatically accessrect1
’s copy of the instance variable -
You can imagine that the
SetLength
method “changes” to this when you callrect1.SetLength()
:Note that we use the “dot” (member access) operator on
rect1
to access itslength
instance variable. -
Similarly, you can imagine that the
SetLength
method “changes” to this when you callrect2.SetLength()
: -
The calling object is automatically “inserted” before any instance variables in a method
-
The keyword
this
is an explicit reference to “the calling object”-
Instead of imagining that the calling object’s name is inserted before each instance variable, you could write the
SetLength
method like this: -
This is valid code (unlike our imaginary examples) and will work exactly the same as our previous way of writing
SetLength
-
When
SetLength
is called withrect1.SetLength(12)
,this
becomes equal torect1
, just likelengthParameter
becomes equal to 12 -
When
SetLength
is called withrect2.SetLength(7)
,this
becomes equal torect2
andlengthParameter
becomes equal to 7
-
Methods and instance variables
-
Using a variable in an expression means reading its value
-
A variable only changes when it is on the left side of an assignment statement; this is writing to the variable
-
A method that uses instance variables in an expression, but does not assign to them, will not modify the object
-
For example, consider the
ComputeArea
method:It reads the current values of
length
andwidth
to compute their product, but the product is returned to the method’s caller. The instance variables are not changed. -
After executing the following code:
rect1
has alength
of 12 and awidth
of 3. The call torect1.ComputeArea()
computes , and thearea
variable is assigned this return value, but it does not changerect1
.
Methods and return values
-
Recall the basic structure of a program: receive input, compute something, produce output
-
A method has the same structure: it receives input from its parameters, computes by executing the statements in its body, then produces output by returning a value
-
For example, consider this method defined in the Rectangle class:
Its input is the parameter
factor
, which is anint
. In the method body, it computes the product of the rectangle’s length andfactor
. The method’s output is the resulting product.
-
-
The
return
statement specifies the output of the method: a variable, expression, etc. that produces some value -
A method call can be used in other code as if it were a value. The “value” of a method call is the method’s return value.
-
In previous examples, we wrote
int area = rect1.ComputeArea();
, which assigns a variable (area
) a value (the return value ofComputeArea()
) -
The
LengthProduct
method can be used like this:When executing the third line of code, the computer first executes the
LengthProduct
method with argument (input) 2, which computes the product . Then it uses the return value ofLengthProduct
, which is 24, to evaluate the expressionrect1.LengthProduct(2) + 1
, producing a result of 25. Finally, it assigns the value 25 to the variableresult
.
-
-
When writing a method that returns a value, the value in the
return
statement must be the same type as the method’s return type-
If the value returned by
LengthProduct
is not anint
, we will get a compile error -
This will not work:
Now that
factor
has typedouble
, the expressionlength * factor
will need to implicitly convertlength
fromint
todouble
in order to make the types match. Then the product will also be adouble
, so the return value does not match the return type (int
). -
We could fix it by either changing the return type of the method to
double
, or adding a cast toint
to the product so that the return value is still anint
-
-
Not all methods return a value, but all methods must have a return type
-
The return type
void
means “nothing is returned” -
If your method does not return a value, its return type must be
void
. If the return type is notvoid
, the method must return a value. -
This will cause a compile error because the method has a return type of
int
but no return statement: -
This will cause a compile error because the method has a return type of
void
, but it attempts to return something anyway:
-
Introduction to UML
-
UML is a specification language for software
- UML: Unified Modeling Language
- Describes design and structure of a program with graphics
- Does not include “implementation details,” such as code statements
- Can be used for any programming language, not just C#
- Used in planning/design phase of software creation, before you start writing code
- Process: Determine program requirements Make UML diagrams Write code based on UML Test and debug program
-
UML Class Diagram elements
- Top box: Class name, centered
- Middle box: Attributes (i.e. instance variables)
- On each line, one attribute, with its name and type
- Syntax:
[+/-] [name]: [type]
- Note this is the opposite order from C# variable declaration: type comes after name
- Minus sign at beginning of line indicates “private member”
- Bottom box: Operations (i.e. methods)
- On each line, one method header, including name, parameters, and return type
- Syntax:
[+/-] [name]([parameter name]: [parameter type]): [return type]
- Also backwards compared to C# order: parameter types come after parameter names, and return type comes after method name instead of before it
- Plus sign at beginning of line indicates “public”, which is what we want for methods
-
UML Diagram for the Rectangle class
- Note that when the return type of a method is
void
, we can omit it in UML - In general, attributes will be private (
-
sign) and methods will be public (+
sign), so you can expect most of your classes to follow this pattern (-
s in the upper box,+
s in the lower box) - Note that there is no code or “implementation” described here: it
does not say that
ComputeArea
will multiplylength
bywidth
- Note that when the return type of a method is
-
Writing code based on a UML diagram
- Each diagram is one class, everything within the box is between the class’s header and its closing brace
- For each attribute in the attributes section, write an instance
variable of the right name and type
- See ”- width: int”, write
private int width;
- Remember to reverse the order of name and type
- See ”- width: int”, write
- For each method in the methods section, write a method header with
the matching return type, name, and parameters
- Parameter declarations are like the instance variables: in UML they have a name followed by a type, in C# you write the type name first
- Now the method bodies need to be filled in - UML just defined the interface, now you need to write the implementation
Variable Scope
Instance variables vs. local variables {#instance-variables-vs.-local-variables}
-
Instance variables: Stored (in memory) with the object, shared by all methods of the object. Changes made within a method persist after method finishes executing.
-
Local variables: Visible to only one method, not shared. Disappear after method finishes executing. Variables we’ve created before in the
Main
method (they are local to theMain
method!). -
Example: In class Rectangle, we have these two methods:
temp
is a local variable withinSwapDimensions
, whilelength
andwidth
are instance variables- The
GetLength
method cannot usetemp
; it is visible only toSwapDimensions
- When
SwapDimensions
changeslength
, that change is persistent — it will still be different whenGetLength
executes, and the next call toGetLength
afterSwapDimensions
will return the new length - When
SwapDimensions
assigns a value totemp
, it only has that value within the current call toSwapDimensions
— afterSwapDimensions
finishes,temp
disappears, and the next call toSwapDimensions
creates a newtemp
Definition of scope
-
Variables exist only in limited time and space within the program
-
Outside those limits, the variable cannot be accessed — e.g. local variables cannot be accessed outside their method
-
Scope of a variable: The region of the program where it is accessible/visible
- A variable is “in scope” when it is accessible
- A variable is “out of scope” when it does not exist or cannot be accessed
-
Time limits to scope: Scope begins after the variable has been declared
- This is why you cannot use a variable before declaring it
-
Space limits to scope: Scope is within the same code block where the variable is declared
- Code blocks are defined by curly braces: everything between matching
{
and}
is in the same code block - Instance variables are declared in the class’s code block (they are
inside
class Rectangle
’s body, but not inside anything else), so their scope extends to the entire class - Code blocks nest: A method’s code block is inside the class’s code block, so instance variables are also in scope within each method’s code block
- Local variables are declared inside a method’s code block, so their scope is limited to that single method
- Code blocks are defined by curly braces: everything between matching
-
The scope of a parameter (which is a variable) is the method’s code block - it is the same as a local variable for that method
-
Scope example:
- The two variables named
temp
have different scopes: One has a scope limited to theSwapDimensions
method’s body, while the other has a scope limited to theSetWidth
method’s body - This is why they can have the same name: variable names must be unique within the variable’s scope. You can have two variables with the same name if they are in different scopes.
- The scope of instance variables
length
andwidth
is the body of classRectangle
, so they are in scope for both of these methods
- The two variables named
Variables with overlapping scopes
-
This code is legal (compiles) but does not do what you want:
-
The instance variable
width
and the local variablewidth
have different scopes, so they can have the same name -
But the instance variable’s scope (the class
Rectangle
) overlaps with the local variable’s scope (the methodUpdateWidth
) -
If two variables have the same name and overlapping scopes, the variable with the closer or smaller scope shadows the variable with the farther or wider scope: the name will refer only to the variable with the smaller scope
-
In this case, that means
width
insideUpdateWidth
refers only to the local variable namedwidth
, whose scope is smaller because it is limited to theUpdateWidth
method. The linewidth = newWidth
actually changes the local variable, not the instance variable namedwidth
. -
Since instance variables have a large scope (the whole class), they will always get shadowed by variables declared within methods
-
You can prevent shadowing by using the keyword
this
, like this:Since
this
means “the calling object”,this.width
means “access thewidth
member of the calling object.” This can only mean the instance variablewidth
, not the local variable with the same name -
Incidentally, you can also use
this
to give your parameters the same name as the instance variables they are modifying:Without
this
, the body of theSetWidth
method would bewidth = width;
, which does not do anything (it would assign the parameterwidth
to itself).
Constants
-
Classes can also contain constants
-
Syntax:
[public/private] const [type] [name] = [value];
-
This is a named value that never changes during program execution
-
Safe to make it
public
because it cannot change — no risk of violating encapsulation -
Can only be built-in types (
int
,double
, etc.), not objects -
Can make your program more readable by giving names to “magic numbers” that have some significance
-
Convention: constants have names in ALL CAPS
-
Example:
The value “12” has a special meaning here, i.e. the number of months in a year, so we use a constant to name it.
-
Constants are accessed using the name of the class, not the name of an object — they are the same for every object of that class. For example:
Reference Types: More Details
- Data types in C# are either value types or reference types
- This difference was introduced in an earlier lecture (Datatypes and Variables)
- For a value type variable (
int
,long
,float
,double
,decimal
,char
,bool
) the named memory location stores the exact data value held by the variable - For a reference type variable, such as
string
, the named memory location stores a reference to the value, not the value itself - All objects you create from your own classes, like
Rectangle
, are reference types
- Object variables are references
-
When you have a variable for a reference type, or “reference variable,” you need to be careful with the assignment operation
-
Consider this code:
-
The output is:
-
The variables
rect1
andrect2
actually refer to the sameRectangle
object, sorect2.SetLength(4)
seems to change the length of “both” rectangles -
The assignment operator copies the contents of the variable, but a reference variable contains a reference to an object — so that’s what gets copied (in
Rectangle rect2 = rect1
), not the object itself -
In more detail:
Rectangle rect1 = new Rectangle()
creates a new Rectangle object somewhere in memory, then creates a reference variable namedrect1
somewhere else in memory. The variable namedrect1
is initialized with the memory address of the Rectangle object, i.e. a reference to the objectrect1.SetLength(8)
reads the address of the Rectangle object from therect1
variable, finds the object in memory, and executes theSetLength
method on that object (changing its length to 8)rect1.SetWidth(10)
does the same thing, finds the same object, and sets its width to 10Rectangle rect2 = rect1
creates a reference variable namedrect2
in memory, but does not create a new Rectangle object. Instead, it initializesrect2
with the same memory address that is stored inrect1
, referring to the same Rectangle objectrect2.SetLength(4)
reads the address of a Rectangle object from therect2
variable, finds that object in memory, and sets its length to 4 — but this is the exact same Rectangle object thatrect1
refers to
-
- Reference types can also appear in method parameters
-
When you call a method, you provide an argument (a value) for each parameter in the method’s declaration
-
Since the parameter is really a variable, the computer will then assign the argument to the parameter, just like variable assignment
- For example, when you write
rect1.SetLength(8)
, there is an implicit assignmentlengthParameter = 8
that gets executed before executing the body of theSetLength
method
- For example, when you write
-
This means if the parameter is a reference type (like an object), the parameter will get a copy of the reference, not a copy of the object
-
When you use the parameter to modify the object, you will modify the same object that the caller provided as an argument
-
This means objects can change other objects!
-
For example, imagine we added this method to the Rectangle class:
It uses the
SetLength
andSetWidth
methods to modify its parameter,otherRect
. Specifically, it sets the parameter’s length and width to its own length and width. -
The
Main
method of a program could do something like this:- First it creates two different
Rectangle
objects (note the two calls tonew
), then it sets the length and width of one object, usingrect1.SetLength
andrect1.SetWidth
- Then it calls the
CopyToOther
method with an argument ofrect2
. This transfers control to the method and (implicitly) makes the assignmentotherRect = rect2
- Since
otherRect
andrect2
are now reference variables referring to the same object, the calls tootherRect.SetLength
andotherRect.SetWidth
within the method will modify that object - After the call to
CopyToOther
, the object referred to byrect2
has a length of 8 and a width of 10, even though we never calledrect2.SetLength
orrect2.SetWidth
- First it creates two different
-