站内搜索: 请输入搜索关键词
当前页面: 图书首页 > NET For Java Developers Migrating To C#

NET For Java Developers Migrating To C#

[ directory ] Previous Section Next Section

4.4 Methods

You 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:

  • Method names should be verbs or verb phrases and in Pascal case (unlike the Camel casing of Java).

  • Do not use Hungarian notation.

  • By default, methods are nonvirtual.

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 public

A public method of an object can be called by any class.

4.4.2 protected

A 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 private

Private 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 internal

Internal 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 static

A static method is a class-level method and does not require an object instance to access the method.

4.4.6 sealed

A 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 extern

Use 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 unsafe

The 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 virtual

In 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 override

An 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 abstract

Use the abstract modifier in a method declaration to indicate that the method or property does not contain implementation. Abstract methods have the following features:

  • An abstract method is implicitly a virtual method.

  • Abstract method declarations are permitted only in abstract classes.

  • Because an abstract 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 abstract void MyMethod();
    
  • The implementation is provided by an overriding method, which is a member of a nonabstract class.

    It is an error to use the static, virtual, private, or override modifier in an abstract method declaration.

4.4.12 new

Use 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:

  • The class is declared abstract because it contains the abstract Work method.

  • The class overrides the ToString() and GetHashCode() methods of System.Object. These are virtual methods of the base class.

  • The class overrides and seals the implementation of the Equals method. This means that subclasses cannot provide an implementation of the Equals method.

  • Methods GetAge(), GetSSN(), GetSalary(), and GetName() are instance methods and have protected, private, internal, and default access, respectively.

  • GetBloodColor() and GetNumberOfEyes() are static methods and have protected and default access levels.

  • CalcInvestments() and CalcDebt() are virtual methods. These can be either overridden or hidden by the subclasses.

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.

  • GetBloodColor() is a static method. Unlike Java, C# does not let you invoke a static method using an object instance.

  • Methods with the default access level behave the same as private methods.

  • The Man class overrides the base class's virtual CalcInvestments() method. It does so by providing the override keyword. Note that despite all our efforts to cast it to the Person object, the invoked method is always on the subclass. The runtime always dispatches a virtual method to the subclass overriding it.

  • The Man class "hides" the base class's virtual CalcDebt() method. It does this by providing the new keyword. Note that it is possible to invoke the base class's implementation by casting out the reference.

  • The Man class must provide an implementation of the abstract method Work().

  • A class can have a method without it having to hide any superclass implementation. If a class uses the override keyword, the compiler will complain if it cannot find a virtual method to override.

4.4.13 Method Inheritance

Virtual 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 Overloading

As 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:

  • Use method overloading to provide different methods that do semantically the same thing.

  • Use method overloading instead of allowing default arguments. Default arguments do not version well and therefore are not allowed in the Common Language Specification (CLS). The following code example illustrates an overloaded String.IndexOf method.

    int String.IndexOf (String name);
    int String.IndexOf (String name, int startIndex);
    
  • Use default values correctly. In a family of overloaded methods, the complex method should use parameter names that indicate a change from the default state assumed in the simple method. For example, in the following code, the first method assumes the search will not be case-sensitive. The second method uses the name ignoreCase rather than caseSensitive to indicate how the default behavior is being changed.

    // Method #1: ignoreCase = false.
      MethodInfo Type.GetMethod(String name);
    // Method #2: Indicates how the default behavior of method
    #1 is being changed.
      MethodInfo Type.GetMethod (String name, Boolean ignoreCase);
    
  • Use a consistent ordering and naming pattern for method parameters. It is common to provide a set of overloaded methods with an increasing number of parameters to allow the developer to specify a desired level of information. The more parameters that you specify, the more detail the developer can specify. In the following code example, the overloaded Execute method has a consistent parameter order and naming pattern variation. Each of the Execute method variations uses the same semantics for the shared set of parameters.

    public class SampleClass {
      readonly string defaultForA = "default value for a";
      readonly string defaultForB = "default value for b";
      readonly string defaultForC = "default value for c";
    
      public void Execute() {
        Execute(defaultForA, defaultForB, defaultForC);
      }
    
      public void Execute (string a) {
        Execute(a, defaultForB, defaultForC);
      }
    
      public void Execute (string a, string b) {
        Execute (a, b, defaultForC);
      }
    
      public virtual void Execute (string a, string b, string c) {
        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.WriteLine(c);
        Console.WriteLine();
      }
    }
    

    This consistent pattern applies if the parameters have different types. Note that the only method in the group that should be virtual is the one that has the most parameters.

  • Use method overloading for variable numbers of parameters. Where it is appropriate to specify a variable number of parameters to a method, use the convention of declaring n methods with increasing numbers of parameters. Provide a method that takes an array of values for numbers greater than n. For example, n=3 or n=4 is appropriate in most cases. The following example illustrates this pattern.

      public class SampleClass {
        public void Execute(string a) {
        Execute(new string[] {a});
      }
    
      public void Execute(string a, string b) {
        Execute(new string[] {a, b});
      }
    
      public void Execute(string a, string b, string c) {
        Execute(new string[] {a, b, c});
      }
    
      public virtual void Execute(string[] args) {
        foreach (string s in args) {
          Console.WriteLine(s);
        }
      }
    }
    
  • If you must provide the ability to override a method, make only the most complete overload virtual and define the other operations in terms of it. The following example illustrates this pattern.

    public class SampleClass {
      private string myString;
    
      public MyClass(string str) {
        this.myString = str;
      }
    
      public int IndexOf(string s) {
        return IndexOf (s, 0);
      }
    
      public int IndexOf(string s, int startIndex) {
        return IndexOf(s, startIndex, myString.Length 梥tart
        Index);
      }
    
      public virtual int IndexOf(string s, int startIndex,
    int count) {
        return myString.IndexOf(s, startIndex, count);
      }
    }
    

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 Parameters

In 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 Keyword

C#, 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 Keyword

The following details about the out keyword have been duplicated here from the Microsoft Visual Studio Help files.

The out method parameter keyword on a method parameter causes a method to refer to the same variable that was passed into the method. Any changes made to the parameter in the method will be reflected in that variable when control passes back to the calling method.

Declaring an out method is useful when you want a method to return multiple values. A method that uses an out parameter can still return a value. A method can have more than one out parameter.

To use an out parameter, the argument must explicitly be passed to the method as an out argument. The value of an out argument will not be passed to the out parameter. A variable passed as an out argument need not be initialized. However, the out parameter must be assigned a value before the method returns.

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 Keyword

The 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 ] Previous Section Next Section