1.43k likes | 1.53k Views
MODULE--2 Creating and Managing Classes and Objects. Modified by: Mohan A. Gholap Original Slides by: Pavan D.M. Introduction.
E N D
MODULE--2Creating and Managing Classes and Objects Modified by: Mohan A. GholapOriginal Slides by: PavanD.M.
Introduction • The Microsoft Windows Runtime for Windows 8 together with the Microsoft .NET Framework available on Windows 7 and Windows 8 contain thousands of classes, and you have used a number of them already, including Console and Exception . • Classes provide a convenient mechanism for modeling the entities manipulated by applications. • An entity can represent a specific item, such as a customer, or something more abstract, such as a transaction. • Part of the design process of any system is concerned with determining the entities that are important to the processes that the system implements, and then performing an analysis to see what information these entities need to hold and what operations they should perform.
Understanding Classification • Class is the root word of the term classification. When you design a class, you systematically arrange information and behavior into a meaningful entity. • This arranging is an act of classification and is something that everyone does—not just programmers. • For example, all cars share common behaviours (they can be steered, stopped, accelerated, and so on) and common attributes (they have a steering wheel, an engine, and so on) • People use the word car to mean an object that shares these common behaviours and attributes. As long as everyone agrees on what a word means, this system works well and you can express complex but precise ideas in a concise form. Without classification, it’s hard to imagine how people could think or communicate at all.
The Purpose of Encapsulation • Encapsulation is an important principle when defining classes. The idea is that a program that uses a class should not have to worry how that class actually works internally. • the program simply creates an instance of a class and calls the methods of that class. • As long as those methods do what they say they will do, the program does not care how they are implemented. • For example, when you call the Console.WriteLinemethod, you don’t want to be bothered with all the intricate details of how the Console class physically arranges for data to be written to the screen. • A class might need to maintain all sorts of internal state information to perform its various methods. This additional state information and activity is hidden from the program that is using the class. • Therefore, encapsulation is sometimes referred to as information hiding. Encapsulation actually has two purposes: • 1) To combine methods and data inside a class; in other words, to support classification • 2) To control the accessibility of the methods and data; in other words, to control the use of the class
Defining and Using a Class In C#, you use the class keyword to define a new class. The data and methods of the class occur in the body of the class between a pair of braces. Here is a C# class called Circle that contains one method (to calculate the circle’s area) and one piece of data (the circle’s radius): class Circle { int radius; double Area() { return Math.PI * radius * radius; } } Note The Math class contains methods for performing mathematical calculations and fields containing mathematical constants. The Math.PIfield contains the value 3.14159265358979323846, which is an approximation of the value of Pi. • The body of a class contains ordinary methods (such as Area) and fields (such as radius)—remember that variables in a class are called fields. • You can use the Circle class in a similar manner to using the other types that you have already met. • You create a variable specifying Circle as its type, and then you initialize the variable with some valid data. Here is an example: Circle c; // Create a Circle c = new Circle(); // Initialize it
Contd… • A point worth highlighting in this code is the use of the new keyword. Previously, when you initialized a variable such as an intor a float, you simply assigned it a value: inti; i= 42; • You cannot do the same with variables of class types. One reason for this is that C# just doesn’t provide the syntax for assigning literal class values to variables. You cannot write a statement such as this: Circle c; c = 42; After all, what is the Circle equivalent of 42? Another reason concerns the way in which memory for variables of class types is allocated and managed by the runtime. newkeyword creates a new instance of a class, more commonly called an object. You can, however, directly assign an instance of a class to another variable of the same type, like this: Circlec; c = new Circle(); Circled; d = c;
Controlling Accessibility • Surprisingly, the Circle class is currently of no practical use. By default, when you encapsulate your methods and data inside a class, the class forms a boundary to the outside world. • Fields (such as radius) and methods (such as Area) defined in the class can be seen by other methods inside the class but not by the outside world—they are private to the class. • So, although you can create a Circle object in a program, you cannot access its radius field or call its Area method, which is why the class is not of much use—yet! • However, you can modify the definition of a field or method with the public and private keywords to control whether it is accessible from the outside: 1) A method or field is private if it is accessible only from the inside of the class. To declare that a method or field is private, you write the keyword private before its declaration. As intimated previously, this is actually the default, but it is good practice to state explicitly that fields and methods are private to avoid any confusion. 2) A method or field is public if it is accessible from both the inside and outside of the class. To declare that a method or field is public, you write the keyword public before its declaration. Here is the Circle class again. This time, Area is declared as a public method and radius is declared as a private field: class Circle { private int radius; public double Area() { return Math.PI * radius * radius; } }
Contd… • Although radius is declared as a private field and is not accessible from outside the class, radius is accessible from inside the Circle class. • The Area method is inside the Circle class, so the body of Area has access to radius. However, the class is still of limited value because there is no way of initializing the radius field. To fix this, you can use a constructor. Naming and Accessibility • The following recommendations are reasonably common and relate to the naming conventions for fields and methods based on the accessibility of class members; however, C# does not enforce these rules: • Identifiers that are public should start with a capital letter. For example, Area starts with A (not a) because it’s public. This system is known as the PascalCasenaming scheme (because it was first used in the Pascal language). • Identifiers that are not public (which include local variables) should start with a lowercase letter. For example, radius starts with r (not R) because it’s private. This system is known as the camelCasenaming scheme. There’s only one exception to this rule: class names should start with a capital letter, and constructors must match the name of their class exactly; therefore, a private constructor must start with a capital letter.
Working with Constructors When you use the new keyword to create an object, the runtime has to construct that object by using the definition of the class. The runtime has to grab a piece of memory from the operating system, fill it with the fields defined by the class, and then invoke a constructor to perform any initialization required. A constructor is a special method that runs automatically when you create an instance of a class. It has the same name as the class, and it can take parameters, but it cannot return a value (not even void). Every class must have a constructor. If you don’t write one, the compiler automatically generates a default constructor for you. You can write your own default constructor quite easily—just add a public method that does not return a value and give it the same name as the class. The following example shows the Circle class with a default constructor that initializes the radius field to 0:
Contd… In this example, the constructor is marked as public. If this keyword is omitted, the constructor will be private (just like any other methods and fields). If the constructor is private, it cannot be used outside the class, which prevents you from being able to create Circle objects from methods that are not part of the Circle class. You might therefore think that private constructors are not that valuable. They do have their uses, but they are beyond the scope of the current discussion.
Contd…. • Having added a public constructor, you can now use the Circle class and exercise its Area method. Notice how you use dot notation to invoke the Area method on a Circle object: Circle c; c = new Circle(); double areaOfCircle = c.Area(); Overloading Constructors You can now declare a Circle variable, use it to reference a newly created Circle object, and then call its Area method. However, there is one last problem. The area of all Circle objects will always be 0 because the default constructor sets the radius to 0 and it stays at 0; the radius field is private, and there is no easy way of changing its value after it has been initialized A constructor is just a special kind of method and it—like all methods—can be overloaded. Just as there are several versions of the Console.WriteLinemethod, each of which takes different parameters, so too can you write different versions of a constructor. So, you can add another constructor to the Circle class, with a parameter that specifies the radius to use, like this:
Contd…. You can then use this constructor when creating a new Circle object, like this Circle c; c = new Circle(45); When you build the application, the compiler works out which constructor it should call based on the parameters that you specify to the new operator. In this example, you passed an int, so the compiler generates code that invokes the constructor that takes an intparameter. You should be aware of an important feature of the C# language: if you write your own constructor for a class, the compiler does not generate a default constructor.
Contd…. • Therefore, if you’ve written your own constructor that accepts one or more parameters and you also want a default constructor, you’ll have to write the default constructor yourself. Partial Classes When you split a class across multiple files, you define the parts of the class by using the partial keyword in each file. For example, if the Circle class is split between two files called circ1.cs (containing the constructors) and circ2.cs (containing the methods and fields), the con-tents of circ1.cs look like this:
Contd…. When you compile a class that has been split into separate files, you must provide all the files to the compiler. In the following exercise, you will declare a class that models a point in two-dimensional space. The class will contain two private fields for holding the x- and y-coordinates of a point and will provide constructors for initializing these fields. You will create instances of the class by using the new keyword and calling the constructors. Write constructors and create objects • Start Visual Studio 2015 if it is not already running. • Open the Classes project located in the \Microsoft Press\Visual CSharp Step By Step\Chapter 7\Windows X\Classes folder in your Documents folder. • In Solution Explorer, double-click the file Program.cs to display it in the Code and Text Editor window. • Locate the Main method in the Program class. • The Main method calls the doWorkmethod, wrapped in a try block and followed by a catch handler. With this try/catch block, you can write the code that would typically go inside Main in the doWorkmethod instead, safe in the knowledge that it will catch and handle any exceptions. The doWorkmethod currently contains nothing but a // TODO: comment.
Contd…. 5) Display the file Point.cs in the Code and Text Editor window. This file defines a class called Point, which you will use to represent the location of a point in two-dimensional space, defined by a pair of x- and y-coordinates. The Point class is currently empty apart from another // TODO: comment. 6) Return to the Program.cs file. Edit the body of the doWorkmethod in the Program class, and replace the // TODO: comment with the following statement: Point origin = new Point(); This statement creates a new instance of the Point class and invokes its default constructor.
7) On the BUILD menu, click Build Solution. The code builds without error because the compiler automatically generates the code for a default constructor for the Point class. However, you cannot see the C# code for this constructor because the compiler does not generate any source language statements. 8) Return to the Point class in the file Point.cs. Replace the // TODO: comment with a public constructor that accepts two intarguments called x and y and that calls the Console.WriteLinemethod to display the values of these arguments to the console, as shown in bold type in the following code example: class Point { public Point(int x, int y) { Console.WriteLine("x:{0}, y:{1}", x, y); } } Contd….
9) On the BUILD menu, click Build Solution. The compiler now reports an error: 'Classes.Point' does not contain a constructor that takes 0 arguments The call to the default constructor in the doWorkmethod is now invalid because there is no longer a default constructor. You have written your own constructor for the Point class, so the compiler does not generate the default constructor. You will now fix this by writing your own default constructor. 10) Edit the Point class, and add a public default constructor that calls Console.WriteLineto write the string “Default constructor called” to the console, as shown in bold type in the following code example. The Point class should now look like this: class Point { public Point() { Console.WriteLine("Default constructor called"); } public Point(int x, int y) { Console.WriteLine("x:{0}, y:{1}", x, y); } } Contd….
Contd… 11) On the BUILD menu, click Build Solution. The program should now build successfully. 12) In the Program.cs file, edit the body of the doWorkmethod. Declare a variable called bottomRightof type Point, and initialize it to a new Point object by using the constructor with two arguments, as shown in bold type in the following code. Supply the values 1366 and 768, representing the coordinates at the lower-right corner of the screen based on the resolution 1366 × 768 (a common resolution for many Windows 8 tablet devices). The doWorkmethod should now look like this: static void doWork() { Point origin = new Point(); Point bottomRight = new Point(1366, 768); } 13) On the DEBUG menu, click Start Without Debugging. • The program builds and runs, displaying the following messages to the console:
Contd… 14) Press the Enter key to end the program and return to Visual Studio 2015. • You will now add two intfields to the Point class to represent the x- and y-coordinates of a point, and you will modify the constructors to initialize these fields. 15) Edit the Point class in the Point.cs file, and add two private fields called x and y of type int, as shown in bold type in the following code. The Point class should now look like this: class Point { private int x, y; public Point() { Console.WriteLine("default constructor called"); } public Point(int x, int y) { Console.WriteLine("x:{0}, y:{1}", x, y); } } You will edit the second Point constructor to initialize the x and y fields to the values of the x and y parameters. There is a potential trap when you do this. If you are not careful, the constructor will look like this:
Contd… public Point(int x, int y) // Don't type this! { x = x; y = y; } Although this code will compile, these statements appear to be ambiguous. How does the compiler know in the statement x = x; that the first x is the field and the second x is the parameter? The answer is that it doesn’t! A method parameter with the same name as a field hides the field for all statements in the method. All this code actually does is assign the parameters to themselves; it does not modify the fields at all. This is clearly not what you want. The solution is to use the thiskeyword to qualify which variables are parameters and which are fields. Prefixing a variable with this means “the field in this object.” 16) Modify the Point constructor that takes two parameters, and replace the Console.WriteLinestatement with the following code shown in bold type: • public Point(int x, int y) • { • this.x= x; • this.y= y; • }
Contd… 17) Edit the default Point constructor to initialize the x and y fields to –1, as follows in bold type. Note that although there are no parameters to cause confusion, it is still good practice to qualify the field references with this: public Point() { this.x= -1; this.y= -1; } 18) On the BUILD menu, click Build Solution. Confirm that the code compiles without errors or warnings. (You can run it, but it does not produce any output yet.) Methods that belong to a class and that operate on the data belonging to a particular instance of a class are called instance methods.
Write and call instance methods 1) In the Classes project in Visual Studio 2015, add the following public instance method called DistanceToto the Point class after the constructors. The method accepts a single Point argument called other and returns a double. The DistanceTomethod should look like this: class Point { …..... public double DistanceTo(Point other) { } } In the following steps, you will add code to the body of the DistanceToinstance method to calculate and return the distance between the Point object being used to make the call and the Point object passed as a parameter. To do this, you must calculate the difference between the x-coordinates and the y-coordinates. 2) In the DistanceTomethod, declare a local intvariable called xDiff, and initialize it with the difference between this.xand other.x, as shown below in bold type: public double DistanceTo(Point other) { intxDiff = this.x - other.x; }
Contd…. 3) Declare another local intvariable called yDiff, and initialize it with the difference between this.yand other.y, as shown here in bold type: public double DistanceTo(Point other) { intxDiff = this.x - other.x; intyDiff = this.y - other.y; } To calculate the distance, you can use Pythagoras theorem and calculate the square root of the sum of the square of xDiffand the square of yDiff. The System.Mathclass provides the Sqrtmethod that you can use to calculate square roots. 4) Declare a variable called distance of type double and use it to hold the result of the calculation just described. public double DistanceTo(Point other) { intxDiff = this.x - other.x; intyDiff = this.y - other.y; double distance = Math.Sqrt((xDiff * xDiff) + (yDiff * yDiff)); }
Contd…. 5) Add the return statement to the end of the DistanceTomethod and return the value in the distance variable: public double DistanceTo(Point other) { intxDiff = this.x - other.x; intyDiff = this.y - other.y; double distance = Math.Sqrt((xDiff * xDiff) + (yDiff * yDiff)); return distance; } • You will now test the DistanceTomethod. 6) Return to the doWorkmethod in the Program class. After the statements that declare and initialize the origin and bottomRight Point variables, declare a variable called distance of type double. Initialize this double variable with the result obtained when you call the DistanceTomethod on the origin object, passing the bottomRightobject to it as an argument. • The doWorkmethod should now look like this: static void doWork() { Point origin = new Point(); Point bottomRight = new Point(1366, 768); double distance = origin.DistanceTo(bottomRight); }
Contd…. 7) Add to the doWorkmethod another statement that writes the value of the distance variable to the console by using the Console.WriteLinemethod. • The completed doWorkmethod should look like this: static void doWork() { Point origin = new Point(); Point bottomRight = new Point(1366, 768); double distance = origin.DistanceTo(bottomRight); Console.WriteLine("Distance is: {0}", distance); } 8) On the DEBUG menu, click Start Without Debugging. 9) Confirm that the value 1568.45465347265 is written to the console window, and then press Enter to close the application and return to Visual Studio 2015.
Understanding static Methods and Data In the preceding exercise, you used the Sqrtmethod of the Math class. Similarly, when looking at the Circle class, you read the PI field of the Math class. If you think about it, the way in which you called the Sqrtmethod or read the PI field was slightly odd. You invoked the method or read the field on the class itself, not on an object of type Math. It is like trying to write Point.DistanceTorather than origin.DistanceToin the code you added in the preceding exercise. what’s happening, and how does this work? You will often find that not all methods naturally belong to an instance of a class; they are utility methods in as much as they provide a useful function that is independent of any specific class instance. The Sqrtmethod is just such an example. If Sqrtwere an instance method of Math, you’d have to create a Math object to call Sqrton: Math m = new Math(); double d = m.Sqrt(42.24); This would be cumbersome. The Math object would play no part in the calculation of the square root. All the input data that Sqrtneeds is provided in the parameter list, and the result is passed back to the caller by using the method’s return value. Objects are not really needed here, so forcing Sqrtinto an instance straitjacket is just not a good idea.
Contd… In C#, all methods must be declared inside a class. However, if you declare a method or a field as static, you can call the method or access the field by using the name of the class. No instance is required. This is how the Sqrtmethod of the Math class is declared: class Math { public static double Sqrt(double d) { ………….... } ……………. ... } A static method does not depend on an instance of the class, and it cannot access any instance fields or instance methods defined in the class; it can use only fields and other methods that are marked as static.
Creating a Shared Field Defining a field as static enables you to create a single instance of a field that is shared among all objects created from a single class In the following example, the static field NumCirclesin the Circle class is incremented by the Circle constructor every time a new Circle object is created: All Circle objects share the same instance of the NumCirclesfield, so the statement NumCircles++; increments the same data every time a new instance is created. Notice that you cannot prefix NumCircleswith the this keyword, as NumCirclesdoes not belong to a specific object. You can access the NumCirclesfield from outside of the class by specifying the Circle class rather than a Circle object. For example: Console.WriteLine("Number of Circle objects: {0}", Circle.NumCircles);
Creating a static Field by Using the const Keyword By prefixing the field with the constkeyword, you can declare that a field is static but that its value can never change. The keyword constis short for constant. A constfield does not use the static keyword in its declaration but is nevertheless static. • For example, here’s how the Math class declares PI as a constfield: class Math { …………... public const double PI = 3.14159265358979323846; }
Understanding static Classes Another feature of the C# language is the ability to declare a class as static. A static class can contain only static members. The purpose of a static class is purely to act as a holder of utility methods and fields. A static class cannot contain any instance data or methods, and it does not make sense to try to create an object from a static class by using the new operator. In fact, you can’t actually create an instance of an object using a static class by using new even if you want to. If you need to perform any initialization, a static class can have a default constructor as long as it is also declared as static. Any other types of constructor are illegal and will be reported as such by the compiler. • If you were defining your own version of the Math class, one containing only static members, it could look like this: public static class Math { public static double Sin(double x) { …………………..... } public static double Cos(double x) { ………………………... } public static double Sqrt(double x) { ………………………... } ……………………………. }
Understanding static Classes In the final exercise in this chapter, you will add a private static field to the Point class and initialize the field to 0. You will increment this count in both constructors. Finally, you will write a public static method to return the value of this private static field. Write static members, and call static methods 1) Using Visual Studio 2015, display the Point class in the Code and Text Editor window. 2) Add a private static field called objectCountof type intto the Point class immediately before the constructors. Initialize it to 0 as you declare it, like this: class Point { ………….... private static intobjectCount = 0; …………..... } 3) Add a statement to both Point constructors to increment the objectCountfield, as shown in bold type in the following code example. • The Point class should now look like this:
Contd… Each time an object is created, its constructor is called. As long as you increment the objectCountin each constructor (including the default constructor), objectCountwill hold the number of objects created so far. This strategy works only because objectCountis a shared static field. If objectCountwere an instance field, each object would have its own personal objectCountfield that would be set to 1.
Contd… • The question now is this: How can users of the Point class find out how many Point objects have been created? • At the moment, the objectCountfield is private and not available outside the class. A poor solution would be to make the objectCountfield publicly accessible. • This strategy would break the encapsulation of the class, and you would then have no guarantee that its value was correct because anyone could change the value in the field. A much better idea is to provide a public static method that returns the value of the objectCountfield. This is what you will do now. 4) Add a public static method to the Point class called ObjectCountthat returns an intbut does not take any parameters. In this method, return the value of the objectCountfield, as follows in bold type: class Point { ........... public static intObjectCount() { return objectCount; } } 5) Display the Program class in the Code and Text Editor window. Add a statement to the doWorkmethod to write the value returned from the ObjectCountmethod of the Point class to the screen, as shown in bold type in the following code example:
Contd… The ObjectCountmethod is called by referencing Point, the name of the class, and not the name of a Point variable (such as origin or bottomRight). Because two Point objects have been created by the time ObjectCountis called, the method should return the value 2. 6) On the DEBUG menu, click Start Without Debugging. Confirm that the message “Number of Point objects: 2” is written to the console window (after the message displaying the value of the distance variable). 7)Press Enter to finish the program and return to Visual Studio 2015.
Anonymous Classes • An anonymous class is a class that does not have a name. This sounds rather strange but is actually quite handy in some situations that you will see later in this book, especially when using query expressions. • You create an anonymous class simply by using the new keyword and a pair of braces defining the fields and values that you want the class to contain, like this: myAnonymousObject= new { Name = "John", Age = 47 }; This class contains two public fields called Name (initialized to the string “John”) and Age (initialized to the integer 47). The compiler infers the types of the fields from the types of the data you specify to initialize them. When you define an anonymous class, the compiler generates its own name for the class, but it won’t tell you what it is. Anonymous classes therefore raise a potentially interesting conundrum: if you don’t know the name of the class, how can you create an object of the appropriate type and assign an instance of the class to it? In the code example shown earlier, what should the type of the variable myAnonymousObjectbe? The answer is that you don’t know—that is the point of anonymous classes! • However, this is not a problem if you declare myAnonymousObjectas an implicitly typed variable by using the varkeyword, like this: varmyAnonymousObject= new { Name = "John", Age = 47 }; Remember that the varkeyword causes the compiler to create a variable of the same type as the expression used to initialize it. In this case, the type of the expression is whatever name the compiler happens to generate for the anonymous class.
Contd… • You can access the fields in the object by using the familiar dot notation, like this: Console.WriteLine("Name: {0} Age: {1}", myAnonymousObject.Name, myAnonymousObject.Age}; • You can even create other instances of the same anonymous class but with different values: varanotherAnonymousObject = new { Name = "Diana", Age = 46 }; The C# compiler uses the names, types, number, and order of the fields to determine whether two instances of an anonymous class have the same type. In this case, the variables myAnonymousObjectand anotherAnonymousObjecthave the same number of fields, with the same name and type, in the same order, so both variables are instances of the same anonymous class. • This means that you can perform assignment statements such as this: anotherAnonymousObject= myAnonymousObject;
Understanding Values and References Copying Value Type Variables and Classes Most of the primitive types built into C#, such as int, float, double, and char (but not string, for reasons that will be covered shortly) are collectively called value types. These types have a fixed size, and when you declare a variable as a value type, the compiler generates code that allocates a block of memory big enough to hold a corresponding value. For example, declaring an intvariable causes the compiler to allocate 4 bytes of memory (32 bits). A statement that assigns a value (such as 42) to the intcauses the value to be copied into this block of memory. Class types, such as Circle (described in Chapter 7), are handled differently. When you declare a Circle variable, the compiler does not generate code that allocates a block of memory big enough to hold a Circle—all it does is allot a small piece of memory that can potentially hold the address of (or a reference to) another block of memory containing a Circle. The memory for the actual Circle object is allocated only when the new keyword is used to create the object. A class is an example of a reference type. Reference types hold references to blocks of memory. To write effective C# programs that make full use of the Microsoft .NET Framework, you need to understand the difference between value types and reference types. Consider the situation in which you declare a variable named ias an intand assign it the value 42. If you declare another variable called copyias an intand then assign ito copyi, copyiwill hold the same value as i(42).
Contd… However, even though copyiand ihappen to hold the same value, there are two blocks of memory containing the value 42: one block for iand the other block for copyi. If you modify the value of i, the value of copyidoes not change. Let’s see this in code: inti = 42; // declare and initialize i intcopyi = i; /* copyi contains a copy of the data in i: iand copyi both contain the value 42 */ i++; /* incrementing i has no effect on copyi; i now contains 43, but copyi still contains 42 */ The effect of declaring a variable c as a class type, such as Circle, is very different. When you declare c as a Circle, c can refer to a Circle object; the actual value held by c is the address of a Circle object in memory. If you declare an additional variable named refc(also as a Circle) and you assign c to refc, refcwill have a copy of the same address as c; in other words, there is only one Circle object, and both refcand c now refer to it. Here’s the example in code: Circlec = new Circle(42); Circlerefc = c;
Contd… The following graphic illustrates both examples. The at sign (@) in the Circle objects represents a reference holding an address in memory: This difference is very important. In particular, it means that the behavior of method parameters depends on whether they are value types or reference types.
Copying Reference Types and Data Privacy If you actually want to copy the contents of a Circle object, c, into a different Circle object, refc, rather than just copying the reference, you must actually make refcrefer to a new instance of the Circle class and then copy the data field by field from c into refc, like this: Circle refc = new Circle(); refc.radius = c.radius; // Don't try this However, if any members of the Circle class are private (like the radius field), you will not be able to copy this data. Instead, you could make the data in the private fields accessible by exposing them as properties, and then using these properties to read the data from c and copy it into refc. Alternatively, a class could provide a Clone method that returns another instance of the same class, but populated with the same data. The Clone method would have access to the private data in an object and could copy this data directly to another instance of the same class.
Contd… • This approach is straightforward if all the private data consists of values, but if one or more fields are themselves reference types, then these reference types also need to provide a Clone method as well, otherwise the Clone method of the Circle class will simply copy a reference to these fields. • This is a process known as a deep copy. The alternative approach, where the Clone method simply copies references, is known as ashallow copy. Use value parameters and reference parameters • Start Microsoft Visual Studio 2015 if it is not already running. • Open the Parameters project located in the \Microsoft Press\Visual CSharp Step By Step\Chapter 8\Windows X\Parameters folder in your Documents folder. The project contains three C# code files: Pass.cs, Program.cs, and WrappedInt.cs. • Display the Pass.cs file in the Code and Text Editor window. This file defines a class called Pass that is currently empty apart from a // TODO: comment.
Contd… 4) Add a public static method called Value to the Pass class, replacing the // TODO: comment. This method should accept a single intparameter (a value type) called paramand have the return type void. The body of the Value method should simply assign the value 42 to param, as shown in bold type in the following code example. namespace Parameters { class Pass { public static void Value(intparam) { param= 42; } } } 5) Display the Program.cs file in the Code and Text Editor window, and then locate the doWorkmethod of the Program class. • The doWorkmethod is called by the Main method when the program starts running. As explained in Chapter 7, the method call is wrapped in a try block and followed by a catch handler.
Contd… 6) Add four statements to the doWorkmethod to perform the following tasks: a. Declare a local intvariable called i, and initialize it to 0. b. Write the value of ito the console by using Console.WriteLine. c. Call Pass.Value, passing ias an argument. d. Write the value of ito the console again. • With the calls to Console.WriteLinebefore and after the call to Pass.Value, you can see whether the call to Pass.Valueactually modifies the value of i. The completed doWorkmethod should look exactly like this: static void doWork() { inti = 0; Console.WriteLine(i); Pass.Value(i); Console.WriteLine(i); } 7)On the DEBUG menu, click Start Without Debugging to build and run the program. 8) Confirm that the value 0 is written to the console window twice.
Contd… The assignment statement inside the Pass.Valuemethod that updates the parameter and sets it to 42 uses a copy of the argument passed in, and the original argument iis completely unaffected. 9) Press the Enter key to close the application. • You will now see what happens when you pass an intparameter that is wrapped inside a class. 10) Display the WrappedInt.cs file in the Code and Text Editor window. This file contains the WrappIntclass, which is empty apart from a // TODO: comment. 11) Add a public instance field called Number of type intto the WrappedIntclass, as shown in bold type below: namespace Parameters { class WrappedInt { public int Number; } } 12) Display the Pass.cs file in the Code and Text Editor window. Add a public static method called Reference to the Pass class. This method should accept a single WrappedIntparameter called paramand have the return type void. The body of the Reference method should assign 42 to param.Number, like this: public static void Reference(WrappedIntparam) { param.Number= 42; }
Contd… 13) Display the Program.cs file in the Code and Text Editor window. Comment out the existing code in the doWorkmethod and add four more statements to perform the following tasks: a. Declare a local WrappedIntvariable called wi, and initialize it to a new WrappedIntobject by calling the default constructor. • b. Write the value of wi.Numberto the console. • c. Call the Pass.Referencemethod, passing wias an argument. • d. Write the value of wi.Numberto the console again. • As before, with the calls to Console.WriteLine, you can see whether the call to Pass.Referencemodifies the value of wi.Number. The DoWorkmethod should now look exactly like this (the new statements are shown in bold type):
Contd… 14) On the DEBUG menu, click Start Without Debugging to build and run the application. • This time, the two values displayed in the console window correspond to the value of wi.Numberbefore and after the call to the Pass.Referencemethod. You should see that the values 0 and 42 are displayed. 15) Press the Enter key to close the application and return to Visual Studio 2015. To explain what the previous exercise shows, the value of wi.Numberis initialized to 0 by the compiler-generated default constructor. The wivariable contains a reference to the newly created WrappedIntobject (which contains an int). The wivariable is then copied as an argument to the Pass.Referencemethod. Because WrappedIntis a class (a reference type), wiand paramboth refer to the same WrappedIntobject. Any changes made to the contents of the object through the paramvariable in the Pass.Referencemethod are visible by using the wivariable when the method completes. The following diagram illustrates what happens when a WrappedIntobject is passed as an argument to the Pass.Referencemethod:
Understanding Null Values and Nullable Types • When you declare a variable, it is always a good idea to initialize it. With value types, it is common to see code such as this: inti = 0;double d = 0.0; • Remember that to initialize a reference variable such as a class, you can create a new instance of the class and assign the reference variable to the new object, like this: Circle c = new Circle(42); • This is all very well, but what if you don’t actually want to create a new object? Perhaps the purpose of the variable is simply to store a reference to an existing object at some later point in your program. In the following code example, the Circle variable copy is initialized, but later it is assigned a reference to another instance of the Circle class: Circle c = new Circle(42); Circle copy = new Circle(99); // Some random value, for initializing copy ……..... copy = c; // copy and c refer to the same object After assigning c to copy, what happens to the original Circle object with a radius of 99 that you used to initialize copy? Nothing refers to it anymore.
Contd… • The important thing to understand for now is that garbage collection is a potentially time-consuming operation; you should not create objects that are never used, as doing so is a waste of time and resources. • You could argue that if a variable is going to be assigned a reference to another object at some point in a program, there is no point initializing it. • But this is poor programming practice and can lead to problems in your code. For example, you will inevitably find yourself in the situation where you want to refer a variable to an object only if that variable does not already contain a reference, as shown in the following code example: The purpose of the if statement is to test the copy variable to see whether it is initialized, but to which value should you compare this variable? The answer is to use a special value called null. In C#, you can assign the null value to any reference variable. The null value simply means that the variable does not refer to an object in memory. You can use it like this:
Contd… The purpose of the if statement is to test the copy variable to see whether it is initialized, but to which value should you compare this variable? The answer is to use a special value called null. In C#, you can assign the null value to any reference variable. The null value simply means that the variable does not refer to an object in memory. You can use it like this:
Using NullableTypes • The null value is useful for initializing reference types. Sometimes you need an equivalent value for value types, but null is itself a reference, and so you cannot assign it to a value type. The following statement is therefore illegal in C#: inti = null; // illegal • However, C# defines a modifier that you can use to declare that a variable is a nullablevalue type. A nullable value type behaves in a similar manner to the original value type, but you can assign the null value to it. You use the question mark (?) to indicate that a value type is nullable, like this: int? i = null; // legal • You can ascertain whether a nullable variable contains null by testing it in the same way as a reference type: if (i == null) ... You can assign an expression of the appropriate value type directly to a nullable variable. The following examples are all legal: