| [ directory ] |
|
4.4 MethodsYou use methods to query and modify the state of the objects. In OOP, method calls are the preferred way to interact with objects. Most class members are methods, so we've adapted the following recommended guidelines for method usage from the Visual Studio help files:
The method signature indicates the return type, any access modifiers or qualifiers preceding the method name, method parameters, and any exceptions that the method throws. The following access modifiers are typically used in front of C# methods. 4.4.1 publicA public method of an object can be called by any class. 4.4.2 protectedA protected member is accessible from within the class in which it is declared, and from within any class derived from the class that declared this member. A protected member of a base class is accessible in a derived class only if the access takes place through the derived class type. 4.4.3 privatePrivate members are accessible only within the body of the class or the struct in which they are declared. Nested types in the same body can also access those private members. 4.4.4 internalInternal methods can be called by any class that exists in the same assembly as the class in which the method is defined. 4.4.5 staticA static method is a class-level method and does not require an object instance to access the method. 4.4.6 sealedA sealed method lets a class override a virtual function and prevents a derived class from overriding that same function. In other words, having sealed on a virtual method stops virtual dispatching. For example:
using System;
class SuperClass {
public virtual string DoIt () { return "DoIt"; }
}
class SubClass : SuperClass {
public override sealed string DoIt () { return "DoIt "; }
static void Main(string[] args) {
SubClass sc = new SubClass();
Console.WriteLine(sc);
}
}
Here, the subclass defines the DoIt() method to be sealed. This allows it to override the virtual method of the superclass, but a subclass of SubClass cannot override that method. 4.4.7 externUse the extern modifier in a method declaration to indicate that the method is implemented externally. This is equivalent to the native keyword in Java. Because an external method declaration provides no actual implementation, there is no method body; the method declaration simply ends with a semicolon, and there are no braces ({ }) following the signature. For example: public static extern int MyMethod(int x); 4.4.8 unsafeThe unsafe keyword denotes an unsafe context, which is required for any operation involving pointers. unsafe is applied as a modifier in the declaration of callable members such as methods, properties, constructors, and extenders (but not static constructors):
static unsafe void FastCopy (byte[] src, byte[] dst, int count)
{
// unsafe context: can use pointers here
}
The scope of the unsafe context extends from the parameter list to the end of the function, so pointers can also be used in the parameter list:
static unsafe void FastCopy ( byte* ps, byte* pd, int count )
{ . . . }
The unsafe keyword has no equivalent in Java. 4.4.9 virtualIn Java, all methods of a given class can be overridden by a subclass by default, unless you use the final keyword on the method. However, in C#, this is not the default behavior. Class methods that can be possibly overridden by a subclass must be marked as virtual. Subclasses can change the implementation of these virtual methods by using the override keyword, explicitly indicating that the subclass is overriding the superclass's behavior. When a virtual method is invoked, the runtime type of the object is checked for an overriding member. The overriding member in the most derived class is called; this member might be the original member if no derived class has overridden the member. By default, methods are nonvirtual. You cannot override a nonvirtual method. In Java, however, all methods are virtual unless otherwise indicated. You cannot use the virtual modifier with the following modifiers: static, abstract, override, and private. 4.4.10 overrideAn override method provides a new implementation of a member inherited from a base class. The method overridden by an override declaration is known as the overridden base method. The overridden base method must have the same signature as the override method. You cannot override a nonvirtual or static method. The overridden base method must be virtual, abstract, or override. An override declaration cannot change the accessibility of the virtual method. Both the override method and the virtual method must have the same access-level modifier. You cannot use the following modifiers to modify an override method: new, static, virtual, and abstract. 4.4.11 abstractUse the abstract modifier in a method declaration to indicate that the method or property does not contain implementation. Abstract methods have the following features:
4.4.12 newUse the new modifier to explicitly hide a member inherited from a base class. To hide an inherited member, declare it in the derived class using the same name, and modify it with the new modifier. In combination with the access modifiers (internal, private, public, and protected), the keywords virtual, override, sealed, new, and abstract provide several degrees of accessibility and extensibility for a class. Listing 4.7 shows the modified Person class with methods that have several access modifiers and the keywords defined here. Listing 4.7 Modified Person Class (C#)
using System;
abstract public class Person {
string name;
string ssn;
int age;
float salary;
public Person (string name, string ssn, int age, float sal) {
this.name = name;
this.ssn = ssn;
this.age = age;
this.salary = sal;
}
private void GetSSN() {
this.ssn = ssn;
}
protected int GetAge() {
return age;
}
internal float GetSalary() {
return salary;
}
string GetName() { return name; }
protected static String GetBloodColor() {
return "Red";
}
static int GetNumberOfEyes() {
return 2;
}
public override String ToString() { return name; }
public override int GetHashCode() { return 12222; }
public override sealed bool Equals(Object o) {
Person p = o as Person;
if (p == null) return false;
return this.name.Equals(p.GetName());
}
public virtual void CalcInvestments() {
Console.WriteLine("Person funds");
}
public virtual void CalcDebt() {
Console.WriteLine("Person debt");
}
public abstract void Work();
}
Note the following details about our Person class:
Listing 4.8 is a subclass Man of the Person class. Note that we have commented out code that won't compile and have specified the reason. Listing 4.8 Subclass of the Person Class (C#)
using System;
public class Man : Person {
public Man (string name, string ssn, int age, float sal): base
(name, ssn, age, sal) {}
public override void Work() {
Console.WriteLine("Works like a dog");
}
public override void CalcInvestments() {
Console.WriteLine("Man funds");
}
public new void CalcDebt() {
Console.WriteLine("Man debt");
}
//Entry point
public static void Main(string[] args) {
Man m = new Man("Jack", "12354544", 23, 40000f);
/******
* Won't compile, cannot call static method using the
* instance reference. Can do this in Java though!
*****/
//Console.WriteLine(m.GetBloodColor());
/******
Won't compile, improper access level.
*****/
//Console.WriteLine(m.GetNumberOfEyes());
//Console.WriteLine(m.GetSSN());
//Console.WriteLine(m.GetName());
Console.WriteLine(m.GetSalary());
Console.WriteLine(m.GetAge());
/**
* All print subclass's implementation of
CalcInvestments()*/
m.CalcInvestments();
Person p = m as Person;
p.CalcInvestments();
((Person)m).CalcInvestments();
m.CalcDebt();
p.CalcDebt();
((Person)m).CalcDebt();
}
}
Note the following details about the subclass.
4.4.13 Method InheritanceVirtual methods in the base class can be either overridden or hidden by the subclass. Also, a subclass must implement any abstract methods present in the base class if the subclass does not want to be abstract itself. In Java, all methods of a class are virtual by default; furthermore, subclasses do not have to explicitly indicate that they are overriding a method. A method with the same signature as that in the base class will be overridden in the subclass. In fact, you can simulate Java-like behavior in C# through method hiding and using the new keyword. A subclass can invoke its superclass's implementation using the base keyword:
class Sub : Super
{
public void DoIt() {
base.Doit();
}
}
Method signatures often include any exceptions that are thrown. Chapter 10 discusses details about inheritance and method exceptions. 4.4.14 Method OverloadingAs in Java, you cannot differentiate a method solely by its return value. For a method to be overloaded it should have parameters that differ from the parameters of any like-named method. The following recommended guidelines for method overloading are from the Visual Studio help files:
When a method is overloaded, to call the right method the compiler compares the parameters the method takes. Methods cannot be overloaded based on their return type. If there is an implicit conversion from the argument type to the parameter type, it will prefer calling that method. Two methods cannot be overloaded if the only difference between them is that the ref parameter of one method is an out on the other. Methods can be overloaded if one of the methods passes parameters by value and other passes parameters by reference. 4.4.15 Method ParametersIn Java, there is not much variety in terms of method parameters. Method parameters are passed by value in Java. Changes made to primitive parameters are lost after the method exits. Object reference parameters are also passed by value. This means that a copy of the reference is made and passed to the method, but the object that the copied reference points to remains the same. The following Java program brings home this point:
import java.util.ArrayList;
import java.util.List;
public class MethodParameterTest {
public static void main(String[] args) {
//Testing primitives
int a = 3;
testPrimitive(a);
System.out.println(a);
//Testing testObjectList1
List list1 = new ArrayList();
System.out.println(list1.size());
testObjectList1(list1);
System.out.println(list1.size());
//Testing testObjectList2
List list2 = new ArrayList();
list2.add("Major");
testObjectList2(list2);
System.out.println( list2.size());
//Testing Strings
String s = "Jack";
testString(s);
System.out.println(s);
}
private static void testPrimitive(int a) {
a *=2;
}
private static void testObjectList1(List l ) {
l.add("Test");
}
private static void testObjectList2(List l ) {
l = null;
}
private static void testString(String s) {
s = "John";
}
}
The preceding program prints the following: 3 0 1 1 1 Jack. The primitive value does not get changed when the testPrimitive() method exits. The primitive a is 3 before the method call, 6 inside the method, and again 3 outside the method. The variable list1, which is a reference to the actual ArrayList object, is passed to the method. A copy of the reference is made on the method call, but this copy points to the same ArrayList() object created just before the method. Using this copied reference, list1 adds an object to the ArrayList() object. The method exits, and the size of the ArrayList() object has now changed. Note that although the reference is copied (by value), the actual object pointed to remains the same; hence, any changes made to that object will be reflected outside the method. The variable list2, which is a reference to the actual ArrayList(), is passed to the method. A copy of the reference is made on the method call, but this copy points to the same ArrayList() object created just before the method. The reference is then set to null. As mentioned in the beginning of this chapter, null is a reference and not an object. Thus, the copied reference is set to null, the original reference to the ArrayList created outside the method remains intact, and the size of the ArrayList() remains the same both before and after the method executes. The last example is tricky. Strings are passed by value to the method. A copy of the string reference is made, and this copy also points to the string literal "Jack". Inside the method, the copied reference is made to point to another literal. This, however, does not affect the original string reference pointing to the literal "Jack". Hence, when the method exits, the reference still points to the same literal. In Java, parameters (both primitive and object references) are always passed by value. Listing 4.9 shows a C# program similar to the Java program just shown. Not surprisingly, C# adheres to the same principle of passing both primitives and object references by value. Listing 4.9 Method Parameter Passing by Value (C#)
using System;
using System.Collections;
public class Test {
public static void Main(string[] args) {
//Testing primitives
int a = 3;
TestPrimitive(a);
Console.WriteLine(a);
//Testing TestObjectList1
ArrayList l = new ArrayList();
Console.WriteLine(l.Count);
TestObjectList1(l);
Console.WriteLine(l.Count);
//Testing testObjectList2
ArrayList ll = new ArrayList();
ll.Add("Major");
Console.WriteLine(ll.Count);
TestObjectList2(ll);
Console.WriteLine(ll.Count);
//Testing Strings
String s = "Jack";
TestString(s);
Console.WriteLine(s);
}
private static void TestPrimitive(int a) {
a *= 2;
}
private static void TestObjectList1(ArrayList l) {
l.Add("Jumbo");
}
private static void TestObjectList2(ArrayList l) {
l = null;
}
private static void TestString(String s) {
s = "John";
}
}
The output of Listing 4.9 is as follows: 3 0 1 1 1 Jack. The ref KeywordC#, however, allows you to change this default passing by value behavior using the ref keyword. To Listing 4.9, we add the ref keyword to all the methods and the parameters when the method is called (see Listing 4.10). Listing 4.10 Using the ref Keyword (C#)
using System;
using System.Collections;
public class Test {
public static void Main(string[] args) {
//Testing primitives
int a = 3;
TestPrimitive(ref a);
Console.WriteLine(a);
//Testing TestObjectList1
ArrayList l = new ArrayList();
Console.WriteLine(l.Count);
TestObjectList1(ref l);
Console.WriteLine(l.Count);
//Testing testObjectList2
ArrayList ll = new ArrayList();
ll.Add("Major");
Console.WriteLine(ll.Count);
TestObjectList2(ref ll);
if (ll != null) {
Console.WriteLine(ll.Count);
}
//Testing Strings
String s = "Jack";
TestString(ref s);
Console.WriteLine(s);
}
private static void TestPrimitive(ref int a) {
a *= 2;
}
private static void TestObjectList1(ref ArrayList l) {
l.Add("Jumbo");
}
private static void TestObjectList2(ref ArrayList l) {
l = null;
}
private static void TestString(ref String s) {
s = "John";
}
}
Here is the output of Listing 4.10: 6 0 1 1 John //Testing primitives int a = 3; TestPrimitive(ref a); Console.WriteLine(a); The primitive value is changed in the method. Hence, the value of the primitive before the method call is 3, inside the method is 6, and outside the method is also 6. //Testing TestObjectList1 ArrayList l = new ArrayList(); Console.WriteLine(l.Count); TestObjectList1(ref l); Console.WriteLine(l.Count); Adding the ref keyword here does not change the fact that the object being added to the ArrayList() in the method is actually being added to the original ArrayList() object created before the method. Hence, the size of the ArrayList() before and after the method call is the same as that of Listing 4.9.
//Testing testObjectList2
ArrayList ll = new ArrayList();
ll.Add("Major");
Console.WriteLine(ll.Count);
TestObjectList2(ref ll);
if (ll != null) {
Console.WriteLine(ll.Count);
}
Adding the ref keyword here passes the reference to the ArrayList as is, and a copy is not made. As a result, when the reference is set to null, the actual reference, and not its copy, is set to null. Therefore, after the method call, the reference is null and we must check for that before calling the Count property on it. Otherwise, we get a NullPointerException. //Testing Strings String s = "Jack"; TestString(ref s); Console.WriteLine(s); The string reference is passed as is to the method. Inside the method it is made to point to a different literal, and hence that change remains after the method call. The out KeywordThe following details about the out keyword have been duplicated here from the Microsoft Visual Studio Help files.
Listing 4.11 shows how an out parameter could be used to create a method that returns multiple values. Listing 4.11 Using the out Keyword to Create Methods That Return Multiple Values (C#)
using System;
public class Test {
public static void Main(string[] args) {
int a;
int b = DoIt(out a);
Console.WriteLine(b);
Console.WriteLine(a);
}
private static int DoIt(out int a) {
a = 2;
return 1;
}
}
The params KeywordThe params keyword lets you specify a method parameter that takes an argument where the number of arguments is variable. No additional parameters are permitted after the params keyword in a method declaration, and only one params keyword is permitted in a method declaration. Listing 4.12 shows the params keyword. Listing 4.12 Using the params Keyword (C#)
using System;
public class Test {
public static void Main(string[] args) {
DoIt(1,2,3,4);
}
private static void DoIt(params int[] list) {
foreach(int i in list)
Console.WriteLine(i);
}
}
|
| [ directory ] |
|