• No results found

Classes and Objects 67 

In document CSharp Handout v1.0 (Page 67-79)

Learning Objectives

After completing the session, you will be able to: ‰ Define Classes and Objects

‰ Explain Properties and Indexers ‰ Describe Access Modifiers

Classes and Objects

You discussed the myriad primitive types built into the C# language, such as int, long, and char. The heart and soul of C#, however, is the ability to create new, complex, programmer-defined types that map cleanly to the objects that make up the problem you are trying to solve. It is this ability to create new types that characterizes an object-oriented language. You specify new types in C# by declaring and defining classes. Also we saw how we can define types with interfaces. Instances of a class are called objects. Objects are created in memory when your program executes.

The difference between a class and an object is the same as the difference between the concept of a car and a particular car like Toyota Camry. You can't drive with the definition of a car, but only with an instance, Toyota Camry. A car class describes what cars are like: they have weight, height, color, wheels and so forth. They also have actions they can take, such as drive, brake, and horn. A particular car will have a specific weight, height, color and so forth.

The following table shows Public Methods of System. Object Objects:

Method Means

Equals Indicates whether two Object instances are equal. GetHashCode Serves as a hash function for a particular type.

GetType Returns the Type of the current instance. Reference

Equals

Determines whether the specified Object instances are the same instance.

ToString Returns a String that represents the current Object.

The huge advantage of classes in object-oriented programming is that they encapsulate the characteristics and capabilities of an entity in a single, self-contained and self-sustaining unit of code. When you want to sort the contents of an instance of a Windows list box control, for

example, you tell the list box to sort itself. How it does so is of no concern; that it does so is all you need to know. Encapsulation, along with polymorphism and inheritance, is one of three cardinal principles of object-oriented programming.

When you define a new class, you define the characteristics of all objects of that class, as well as their behaviors. For example, if you create a listbox, a control that is very useful for presenting a list of choices to the user and enabling the user to select from the list. Listboxes have a variety of characteristics: height, width, location, and text color, for example. Programmers have also come to expect certain behaviors of listboxes — they can be opened, closed, sorted, and so on.

To define a new type or class, you first declare it and then define its methods and fields. You declare a class using the class keyword. The complete syntax is as follows:

[attributes] [modifiers] class identifier [:base-list] { class-body }[;] ‰ Attributes (Optional): Attributes hold additional declarative information.

‰ Modifiers (Optional): The allowed modifiers are new, static, virtual, abstract, override, and a valid combination of the four access modifiers

‰ Identifier: The class name.

‰ Base-list (Optional): A list that contains the base class and any implemented interfaces, separated by commas.

‰ Class-body: Declarations of the class members.

The following example demonstrates the usage of class and object. using System;

namespace Sum {

class Sum {

void add(int a, int b) {

int result; result=a+b;

Console.WriteLine("The sum of two integers is :" + result); }

static void Main(string[] args) {

Sum objSum= new Sum(); objSum.add(1, 2); }

} }

Properties

Properties allow clients to access class state as if they were accessing member fields directly, while actually implementing that access through a class method. This is ideal. The client wants direct access to the state of the object and does not want to work with methods. The class designer, however, wants to hide the internal state of his class in class members, and provide indirect access through a method.

By decoupling the class state from the method that accesses that state, the designer is free to change the internal state of the object as needed. When the Time class is first created, the Hour value might be stored as a member variable. When the class is redesigned, the Hour value might be computed, or retrieved from a database. If the client had direct access to the original Hour member variable, the change to computing the value would break the client. By decoupling and forcing the client to go through a method (or property), the Time class can change how it manages its internal state without breaking client code.

Properties meet both goals: they provide a simple interface to the client, appearing to be a member variable. They are implemented as methods, however, providing the data hiding required by good object-oriented design,

The following code snippet shows how to create and use a property. The Time class has the Hours property implementation. Notice that the first letter of the first word is capitalized. That's the only difference between the names of the property Hours and the field iHours. The property has two accessors, get and set. The get accessor returns the value of the iHours field. The set accessor sets the value of the iHours field with the contents of value. The value shown in the set accessor is a C# reserved word.

public class Time {

private int iHours = 0; public int Hours

{ get { return iHours; } set { iHours = value; } } }

Whenever you reference the property (other than to assign to it), the get accessor is invoked to read the value of the property:

Time oTime = new Time();

int iCurrentHour = oTime.Hour;

Properties can be made read-only or write-only. This is accomplished by having only a get or set accessor in the property implementation.

Indexers

There are times when it is desirable to access a collection within a class as though the class itself were an array. For example, suppose you create a list box control named myListBox that contains a list of strings stored in a one-dimensional array, a private member variable named myStrings. A list box control contains member properties and methods in addition to its array of strings.

However, it would be convenient to be able to access the list box array with an index, just as if the list box were an array. For example, such a property would permit statements like the following: string theFirstString = myListBox[0];

string theLastString = myListBox[Length-1];

An indexer is a C# construct that allows you to access collections contained by a class using the familiar [] syntax of arrays. An indexer is a special kind of property and includes get( ) and set() methods to specify its behavior. You declare an indexer property within a class using the following syntax:

type this [type argument]{get; set;}

The return type determines the type of object that will be returned by the indexer, while the type argument specifies what kind of argument will be used to index into the collection that contains the target objects. Although it is common to use integers as index values, you can index a collection on other types as well, including strings. You can even provide an indexer with multiple parameters to create a multidimensional array! This keyword is a reference to the object in which the indexer appears. As with a normal property, you also must define get( ) and set( ) methods that determine how the requested object is retrieved from or assigned to its collection.

public class SimpleIndexer {

private string[] myData; public IntIndexer(int size) {

myData = new string[size]; for (int i=0; i < size; i++) {

myData[i] = "empty";

} }

public string this[int pos]

{ get { return myData[pos]; } set { myData[pos] = value; } } }

//usage of the indexer

SimpleIndexer oSimpleIndexer = new SimpleIndexer(10) oSimpleIndexer[0] = "Cognizant Academy";

Using an integer is a common means of accessing arrays in many languages, but the C# Indexer goes beyond this. Indexers can be declared with multiple parameters and each parameter may be a different type. Additional parameters are separated by commas, the same as a method

parameter list. Valid parameter types for Indexers include integers, enums, and strings. Additionally, Indexers can also be overloaded.

class OvrIndexer {

private string[] myData; private int arrSize;

public OvrIndexer(int size) {

arrSize = size;

myData = new string[size]; for (int i=0; i < size; i++) {

myData[i] = "empty";

} }

public string this[int pos] { get { return myData[pos]; } set { myData[pos] = value; } }

public string this[string data]

{ get

{

int count = 0;

for (int i=0; i < arrSize; i++) { if (myData[i] == data) { count++; } } return count.ToString(); } set {

for (int i=0; i < arrSize; i++) {

if (myData[i] == data) { myData[i] = value; } } } }

//usage of an overloaded indexer

OvrIndexer myInd = new OvrIndexer(10); myInd[9] = "Some Value";

myInd[3] = "Another Value"; myInd[5] = "Any Value"; myInd["empty"] = "no value";

The reason both Indexers in above code can coexist in the same class is because they have different signatures. An Indexer signature is specified by the number and type of parameters in an Indexers parameter list. The class will be smart enough to figure out which Indexer to invoke, based on the number and type of arguments in the Indexer call. An indexer with multiple parameters would be implemented something like this:

public object this[int param1, ..., int paramN] {

get

{

// process and return some class data }

set {

// process and assign some class data }

}

Access Modifiers

Access modifiers are keywords used to specify the declared accessibility of a member or a type. This section introduces the four access modifiers:

‰ public ‰ protected ‰ internal ‰ private

The following five accessibility levels can be specified using the access modifiers: ‰ public: Access is not restricted.

‰ protected: Access is limited to the containing class or types derived from the containing class.

‰ Internal: Access is limited to the current assembly.

‰ protected internal: Access is limited to the current assembly or types derived from the containing class.

‰ private: Access is limited to the containing type.

This section also introduces the following:

‰ Accessibility Levels: Using the four access modifiers to declare five levels of accessibility.

‰ Accessibility Domain: Specifies where, in the program sections, a member can be referenced.

‰ Restrictions on Using Accessibility Levels: A summary of the restrictions on using declared accessibility levels.

Accessibility levels

Use the access modifiers, public, protected, internal, or private to specify one of the following declared accessibilities for members.

Declared accessibility Meaning public Access is not restricted.

protected Access is limited to the containing class or types derived from the containing class.

internal Access is limited to the current assembly.

protected internal Access is limited to the current assembly or types derived from the containing class.

private Access is limited to the containing type.

Only one access modifier is allowed for a member or type, except when using the protected internal combination.

Access modifiers are not allowed on namespaces. Namespaces have no access restrictions.

Depending on the context in which a member declaration takes place, only certain declared accessibilities are permitted. If no access modifier is specified in a member declaration, a default accessibility is used.

Top-level types, which are not nested into other types, can only have internal or public

accessibility. The default accessibility for these types is internal. Nested types, which are members of other types, can have declared accessibilities as indicated in the following table.

Members Default member accessibility Allowed declared accessibility of the member

Members Default member accessibility Allowed declared accessibility of the member

class private public

protected internal private

protected internal

interface public None

struct private public

internal private

The accessibility of a nested type depends on its accessibility domain, which is determined by both the declared accessibility of the member and the accessibility domain of the immediately

containing type. However, the accessibility domain of a nested type cannot exceed that of the containing type.

Accessibility Domain

The accessibility domain of a member specifies where, in the program sections, a member can be referenced. If the member is nested within another type, then its accessibility domain is determined by both the accessibility level of the member and the accessibility domain of the immediately containing type.

The accessibility domain of a top-level type is at least the program text of the project in which it is declared. That is, the entire source files of this project. The accessibility domain of a nested type is at least the program text of the type in which it is declared. That is, the type body, including any nested types. The accessibility domain of a nested type never exceeds that of the containing type. These concepts are demonstrated in the following example.

Example:

This example contains a top-level type, T1, and two nested classes, M1 and M2. The classes contain fields with different declared accessibilities. In the Main method, a comment follows each statement to indicate the accessibility domain of each member. Notice that the statements that attempt to reference the inaccessible members are commented out. If you want to see the compiler errors caused by referencing an inaccessible member, remove the comments one at a time. // cs_Accessibility_Domain.cs using System; namespace AccessibilityDomainNamespace { public class T1 {

public static int publicInt; internal static int internalInt;

public class M1 {

public static int publicInt; internal static int internalInt;

private static int privateInt = 0; // CS0414 }

private class M2 {

public static int publicInt = 0; internal static int internalInt = 0;

private static int privateInt = 0; // CS0414 }

}

class MainClass {

static void Main() {

// Access is unlimited: T1.publicInt = 1;

// Accessible only in current assembly: T1.internalInt = 2;

// Error: inaccessible outside T1: // T1.myPrivateInt = 3;

// Access is unlimited: T1.M1.publicInt = 1;

// Accessible only in current assembly: T1.M1.internalInt = 2;

// Error: inaccessible outside M1: // T1.M1.myPrivateInt = 3; // Error: inaccessible outside T1: // T1.M2.myPublicInt = 1;

// Error: inaccessible outside T1: // T1.M2.myInternalInt = 2; // Error: inaccessible outside M2: // T1.M2.myPrivateInt = 3; }

} }

Restrictions on using Accessibility levels

When you declare a type, it is essential to see if that type has to be at least as accessible as another member or type. For example, the direct base class must be at least as accessible as the derived class. The following declarations will result in a compiler error, because the base class BaseClass is less accessible than MyClass:

class BaseClass {...}

public class MyClass: BaseClass {...} // Error

The following table summarizes the restrictions on using declared accessibility levels:

Context Remarks

Classes The direct base class of a class type must be at least as accessible as the class type itself.

Interfaces The explicit base interfaces of an interface type must be at least as accessible as the interface type itself.

Delegates The return type and parameter types of a delegate type must be at least as accessible as the delegate type itself.

Constants The type of a constant must be at least as accessible as the constant itself. Fields The type of a field must be at least as accessible as the field itself.

Methods The return type and parameter types of a method must be at least as accessible as the method itself.

Properties The type of a property must be at least as accessible as the property itself. Events The type of an event must be at least as accessible as the event itself.

Indexers The type and parameter types of an indexer must be at least as accessible as the indexer itself.

Operators The return type and parameter types of an operator must be at least as accessible as the operator itself.

Constructors The parameter types of a constructor must be at least as accessible as the constructor itself.

The following example contains erroneous declarations of different types. The comment following each declaration indicates the expected compiler error:

// Restrictions_on_Using_Accessibility_Levels.cs

// CS0052 expected as well as CS0053, CS0056, and CS0057

// To make the program work, change access level of both class B // and MyPrivateMethod() to public.

// A delegate:

delegate int MyDelegate(); class B

{

// A private method:

static int MyPrivateMethod() { return 0; } } public class A {

// Error: The type B is less accessible than the field A.myField. public B myField = new B();

// Error: The type B is less accessible // than the constant A.myConst.

public readonly B myConst = new B(); public B MyMethod()

{

// Error: The type B is less accessible // than the method A.MyMethod.

return new B(); }

// Error: The type B is less accessible than the property A.MyProp public B MyProp { set { } }

MyDelegate d = new MyDelegate(B.MyPrivateMethod);

// Even when B is declared public, you still get the error: // "The parameter B.MyPrivateMethod is not accessible due to protection level."

public static B operator +(A m1, B m2) {

// Error: The type B is less accessible // than the operator A.operator +(A,B) return new B();

}

static void Main() {

} }

Summary

‰ Following are the five types of access levels: o Public

o Protected o Internal

o Protected internal o Private

‰ Properties combine aspects of both fields and methods. ‰ Indexers are also called smart arrays in C#.

‰ Indexers treat an object as an array.

Test your Understanding

1. State the differences between protected internal and internal access levels. 2. Explain the usage of Internal Keyword.

3. What are the accessors in properties? 4. What are indexers?

In document CSharp Handout v1.0 (Page 67-79)