10.1. Introduction to Class
10.1.1. Overview
To understand class and object-oriented programming (OOP), some important basic concepts learn first inlcude:
Class: A class is a blueprint, prototype, or template.
Class Members: In C#, a class contains members, which include fields, properties, methods, and events.
Class Instance: An
object
is aninstance
of a class. Namely, an object is created based on a class and therefore is an instance of that class. An object can be created using thenew
keyword.
There are related terminology that you will see about class and OOP:
Field: A field is a variable of any type that is declared directly in a class or struct. Fields are members of their containing type. A field stores a piece of data within an object. It acts like a variable and may have a different value for each instance of a type. [2]
Property: Properties expose fields. Fields should (almost always) be kept private to a class and accessed via get and set properties.
The this Keyword: In C#, the this keyword refers to the current instance of a class.
Reference Types: A type that is defined as a class is a reference type. Other types include: interface, struct, delegate, or enum.
Constructor: In C#, a constructor is called when an object is created to set default values. A constructor must have the same name as the enclosing class. Using constructor is not obligatory in C#. If no constructors are specified in a class, the compiler automatically creates a parameterless constructor.
10.1.2. Declaring Classes
To declare a class, you need to use the class
keyword and give a unique identifier
(name) to the class. For example,
you want to create a customer class to include some data and methods in one unit:
//[access modifier] - [class] - [identifier]
public class Customer
{
// Fields, properties, methods and events go here...
}
Attributes to be considered when creating a class include:
- Access Modifiers: The default access for a class type is internal. Basic levels of access for a class include [1] :
public: Access is not restricted. Anyone code can create instances of this class.
protected: Access is limited to the containing class or types derived from the containing class.
internal: Access is limited to the current assembly. [3]
private: Access is limited to the containing type.
Class Identifier: A unique name for the class. An identifier is the name you assign to a type (class, interface, struct, delegate, or enum), member, variable, or namespace. An identifier begins with a letter or underscore. As a convention, use PascalCase for class names and method names.
Class Body: The class body is surrounded by { }. The behavior and data are defined in the class body, including fields, properties, methods, and events on a class and are collectively referred to as class members.
Base Class or Super Class: Optional. Name of the class’s superclass; preceded by a colon (:).
Interfaces: Interface names implemented by the class; preceded by a colon (:).
10.1.3. Using a Class
In the following example, there is a class called Program
with a Main method. We define another class
called DoMath
and then, in the Main method of the Program class, 1) instantiate an object of the DoMath class by
using the new
keyword and 2) use the dot notation (.
) to access a method defined in DoMath:
internal class Program
{
static void Main(string[] args)
{
DoMath doMath = new DoMath(); // create an object using the new keyword
int aSum = doMath.Sum(2, 2); // use the [object].[method] syntax to use the methods
Console.WriteLine(aSum); // output 4
}
}
class DoMath // class accessability level is default to internal
{
public int Sum(int num1, int num2)
{
var total = num1 + num2;
return total;
}
}
In addition to store data as local variables and call methods in the same class,
now we have alternatives for storing and accessing data in the methods within
a new class that we write. Just like using the UI
class or Random
class, we
now can better design, extend, and organize program functionalities.
When you use classes like StreamReader, you create a new StreamReader object using the new
keyword and then use the ReadLine
method (example in csharprepl):
> StreamReader reader = new StreamReader("ui.cs");
> string line = reader.ReadLine();
> line
"using System;"
>
As you can see, we are using the member access operator (.)
to access a member
of a namespace or a type. Calls to instance methods are always attached to a specific object.
That has always been the part through the .
of
object
.
method(
…)
10.1.4. Constructors
The constructor is a special method in the class:
Its
name
is the same as the name of its type. For example, if we create a class namedContact
, then the constructor method is also calledContact
.Its method
signature
includes only an optional access modifier, the method name, and its parameter listIts method
signature
hasno return type
(and nostatic
). Implicitly you are creating a default object from the class.The constructor can have parameters like a regular method. That means giving values to its fields. For example, you may want to store this state (the current condition of a system, such as the values of the variables) in instance variables
name
,phone
andemail
if you are creating a Contact type:
public Contact(string fullName, string phoneNumber, string emailAddress)
{
name = fullName;
phone = phoneNumber;
email = emailAddress;
}
While the local variables in the formal parameters disappear after the constructor terminates, we want the data to live on as the state of the object. In order to remember state after the constructor terminates, we must make sure the information gets into the instance variables for the object. This is the basic operation of most constructors: Copy desired formal parameters in to initialize the state in the fields. That is all our simple code above does.
To further explain how constructors work, take a look at the following code:
internal class Program
{
static void Main(string[] args)
{
CheckID check = new CheckID("taylor swift");
}
}
class CheckID
{
public string name; // this is an instance field (variable)
public CheckID(string name)
{
this.name = name; // The "this" keyword refers to the current instance of the class
}
public void PrintResult()
{
// ... if ID for this name exists ...
Console.WriteLine($"{name} has an existing ID!"); // using the "name" variable in method
} // output "taylor swift has an existing ID!"
// ....
// ....
}
10.1.5. Static Classes & Methods
In some conditions you may want to create static classes that provide static methods without
instantiation. That means you do not need to use the new
operator keyword to create a variable
of the class type. Also, without the class instance variable, you access the members of a static
class by using the class name itself. For example, if you have a static class called SomeMath:
static class SomeMath
{
public static int Sum(int a, int b) // a public static method
{
return a + b;
}
}
To use the Sum method in the SomeMath class, you call it directly without creating a new object:
internal class Program
{
static void Main(string[] args)
{
int aSum = SomeMath.Sum(2, 2);
Console.WriteLine(aSum); // output 4
}
}
As another example, in the .NET Class Library, the static
System.Math
class contains methods that perform mathematical operations, without any requirement to store or
retrieve data that is unique to a particular instance of the Math
class. You can use the math methods directly in your code by referring to the static class name Math
:
double dub = -3.14;
Console.WriteLine(Math.Abs(dub)); // 3.14
Console.WriteLine(Math.Floor(dub)); // -4
Console.WriteLine(Math.Round(Math.Abs(dub))); // 3
10.1.6. Properties
Note
Although the content are still valid, this section will be updated later to reflect the more current syntax and discussion of properties such as in Using Properties.
A property is a member that provides a flexible mechanism to read, write, or compute the value of a data field. Properties appear as public data members, but they’re implemented as special methods called accessors.
10.1.6.1. Getters
In instance methods you have an extra way of getting data in and out of the method: Reading or setting instance variables (fields). The simplest methods do nothing but reading or setting instance variables. We start with those:
The private
in front
of the field declarations was important to keep code outside the
class from messing with the values. On the other hand we do want
others to be able to inspect the name, phone and email,
so how do we do that? Use public methods.
Since the fields are accessible anywhere inside the class’s instance methods, and public methods can be used from outside the class, we can simply code
public string GetName()
{
return name;
}
public string GetPhone()
{
return phone;
}
public string GetEmail()
{
return email;
}
These methods allow one-way communication of the name, phone and email values out from the object. These are examples of a simple category of methods: A getter simply returns the value of a part of the object’s state, without changing the object at all.
Note again that there is no static
in the method heading.
The field value for the current Contact is returned.
A standard convention that we are following: Have getter methods names start with “Get”, followed by the name of the data to be returned.
In this first simple version of Contact we add one further method, to print all the contact information with labels.
public void Print()
{
Console.WriteLine (@"Name: {0}
Phone: {1}
Email: {2}", name, phone, email);
}
Again, we use the instance variable names, plugging them into a format string.
Remember the @
syntax
for multiline strings gives us verbatim string literals that are interpreted literally
without escape sequences.
You can see and see the entire Contact class in contact1/contact1.cs.
This is your first complete class defining a new type of object. Look carefully to get used to the features introduced, before we add more ideas:
10.1.6.2. This Object
We will be making an elaboration on the Contact class from here on. We introduce new parts individually, but the whole code is in contact2/contact2.cs.
The current object is implicit inside a constructor or instance method definition,
but it can be referred to explicitly. It is called this
.
In a constructor or instance method, this
is automatically a legal
local variable to reference.
You usually do not need to
use it explicitly, but you could. For example the current Contact
object’s
name
field could be referred to as either this.name
or the shorter plain name
.
In our next version of the Contact class we will see several places where
an explicit this
is useful.
In the first version of the constructor, repeated here,
public Contact(string fullName, string phoneNumber, string emailAddress)
{
name = fullName;
phone = phoneNumber;
email = emailAddress;
}
we used different names for the instance variables and the formal parameter names that we used to initialize the instance variables. We chose reasonable names, but we are adding extra names that we are not going to use later, and it can be confusing. The most obvious names for the formal parameters that will initialize the instance variables are the same names.
If we are not careful, there is a problem with that. An instance variable, however named, and a local variable are not the same. This is nonsensical:
public Contact(string name, string phone, string email)
{
name = name; // ????
...
Logically we want this pseudo-code in the constructor:
instance variable
name
=
local variablename
We have to disambiguate the two uses. The compiler always looks for
local variable identifiers first, so plain name
will refer to the local
variable name
declared in the formal parameter list.
This local variable identifier hides the matching instance variable
identifier. We have to do something else to refer to the instance variable.
The explicit this
object comes to the rescue: this.name
refers to
a part of this object. It must refer to the
instance variable, not the local variable. Our alternate constructor is:
public Contact(string name, string phone, string email)
{
this.name = name;
this.phone = phone;
this.email = email;
}
10.1.6.3. Setters
The original version of Contact makes a Contact object be immutable: Once it is created with the constructor, there is no way to change its internal state. The only assignments to the private instance variables are the ones in the constructor. In real life people can change their email address. We might like to allow that with our Contact objects. Users can read the data in a Contact with the getter methods. Now we need setter methods. The naming conventions are similar: start with “Set”. In this case we must supply the new data, so setter methods need a parameter:
public void SetPhone(string newPhone)
{
phone = newPhone;
}
In SetPhone
, like in our original constructor, we illustrate using a
new name for the parameter that sets the instance variable. For
comparison we use the alternate identifier matching approach in the
other setter:
public void SetEmail(string email)
{
this.email = email;
}
Now we can alter the contents of a Contact:
Contact c1 = new Contact("Marie Ortiz", "773-508-7890", "mortiz2@luc.edu");
c1.SetEmail ("maria.ortiz@gmail.com");
c1.SetPhone("555-555-5555");
c1.Print();
would print
Name: Marie Ortiz
Phone: 555-555-5555
Email: maria.ortiz@gmail.com
10.1.7. ToString Override
All object in C# have a ToString
method because all types inherit from the base class
System.Object. It converts an object to its string representation for display. It is used
to perform internal string concatenation and Write.
10.1.7.1. Default ToString behavior
By default, ToString returns returns the fully qualified name
of the type of the Object. For example:
1namespace IntroCSCS
2{
3 class Animal
4 {
5 public string name;
6 public Animal(string name)
7 {
8 this.name = name; }
9 }
10 }
11
12 public class TestAnimal
13 {
14 public static void Main()
15 {
16 Animal frog = new Animal("Froggy");
17 Console.WriteLine(frog.ToString()); ///// print: IntroCSCS.Animal; Animal is the type
18 Console.WriteLine(frog.name); ///// print: Froggy
19 }
20 }
21}
10.1.7.2. Overriding ToString
Think more generally about string representations:
All the built-in type values can be concatenated into strings with the ‘+’ operator,
or displayed with Console.Write
. We would like that behavior with our custom types,
too.
Types frequently override
the Object.ToString method to provide a more suitable string
representation or display of a particular type instead of the default fully qualified type name.
For example:
1class Employee
2{
3 public string Name { get; set; }
4 public int Salary { get; set; }
5
6 public override string ToString()
7 {
8 return "Employee: " + Name + " " + Salary; ///// returns object data
9 }
10}
11
12class Test
13{
14 public static void Main()
15 {
16 Employee employee = new Employee { Name = "John", Salary = 100000 };
17 Console.WriteLine(employee.ToString()); ///// print: Employee: John 100000
18 Console.WriteLine(employee); ///// print: Employee: John 100000
19 ///// as ToString is the default output
20
21 }
22}
See what the ToString method does now: it uses the object state (e.g., the values of variables name and salary in this case) and implicitly covert and return a single string representation of the object.
10.1.7.3. Print() Customization
You can further customize the ToString overriding. For example, in the preceding Employee
class,
in addition to the following ToString
override, you might like to have a convenience method
Print
using ToString
. We want one instance method, Print
to call another instance
method ToString
for the same object:
public void Print()
{
Console.WriteLine(ToString());
// Console.WriteLine(this); ///// "this" returns the same as ToString()
}
Now take a look at the test and output:
Employee employee = new Employee { Name = "John", Salary = 100000 };
Console.WriteLine(employee.ToString());
Console.WriteLine(employee);
employee.Print();
tcn85@mac:~/workspace/introcscs/Ch10ClassesLab$ dotnet run
Employee: John 100000
Employee: John 100000
Employee: John 100000
footnote