Advanced C# Techniques (C# vs Java)
C# provides some useful language features, such as indexers, attributes, and delegates, which enable advanced programming techniques that are not available in Java.
Indexers
Indexers provide a way to access a class or struct in the same way as an array. For example, you can have a class that represents a single department in a company. The class could contain the names of all employees in the department, and indexers could allow you to access these names as follows:
sales[0] = "Nikki";
sales[1] = "Becky";
Indexers are enabled by defining a property with the following signature, for example, in the class definition:
public string this [int index] //indexer
You then provide get and set methods as you would for a normal property, and it is these accessors that specify what internal member is referred to when the indexer is used.
In the following simple example, you create a class called Department
that uses indexers to access the employees in that department, internally represented as an array of strings:
public class Department
{
private string name;
private const int MAX_EMPLOYEES = 10;
private string[] employees = new string[MAX_EMPLOYEES]; //employee array
public Department(string departmentName) //constructor
{
name = departmentName;
}
public string this [int index] //indexer
{
get
{
if (index >= 0 && index < MAX_EMPLOYEES)
{
return employees[index];
}
else
{
throw new System.IndexOutOfRangeException();
}
}
set
{
if (index >= 0 && index < MAX_EMPLOYEES)
{
employees[index] = value;
}
else
{
throw new System.IndexOutOfRangeException();
}
}
}
// code for the rest of the class...
}
You can then create an instance of this class and access it as shown in the following code example:
class TestDepartment
{
static void Main()
{
Department sales = new Department("Sales");
sales[0] = "Nikki";
sales[1] = "Becky";
System.Console.WriteLine("The sales team is {0} and {1}", sales[0], sales[1]);
}
}
The output is:
The sales team is Nikki and Becky
For more information, see Indexers (C# Programming Guide).
Attributes
C# introduces a new mechanism for adding declarative information about types called attributes. Extra information about a type is placed inside declarative tags that precede the type definition. The following examples show how you can use .NET Framework attributes to decorate a class or method.
In the example below, the GetTime
method is marked as an XML Web service by adding the WebMethodAttribute attribute.
public class Utilities : System.Web.Services.WebService
{
[System.Web.Services.WebMethod] // Attribute
public string GetTime()
{
return System.DateTime.Now.ToShortTimeString();
}
}
Adding the WebMethod attribute makes the .NET Framework automatically take care of the XML/SOAP interchange necessary to call this function. Calling this Web service retrieves the following value:
<?xml version="1.0" encoding="utf-8" ?>
<string xmlns="https://tempuri.org/">7:26 PM</string>
In the following example, the Employee
class is marked as serializable by adding the SerializableAttribute attribute. While the Salary
field is marked as public, it will not be serialized as it is marked with the NonSerializedAttribute attribute.
[System.Serializable()]
public class Employee
{
public int ID;
public string Name;
[System.NonSerialized()] public int Salary;
}
For more information, see Creating Custom Attributes (C# Programming Guide).
Delegates
Languages such as C++, Pascal, and others support the concept of function pointers that permit you to choose which function you want to call at run time.
Java does not provide any construct with the functionality of a function pointer, but C# does. Through the use of the Delegate class, a delegate instance encapsulates a method that is a callable entity.
For instance methods, the delegate consists of an instance of the containing class and a method on the instance. For static methods, a callable entity consists of a class and a static method on the class. Thus, a delegate can be used to invoke a function of any object, and delegates are object-oriented, type- safe, and secure.
There are three steps for defining and using delegates:
Declaration
Instantiation
Invocation
You declare a delegate with the following syntax:
delegate void Del1();
This delegate can then be used to reference any function that returns void and does not take any arguments.
Similarly, to create a delegate for any function that takes a string parameter and returns a long, you would use the following syntax:
delegate long Del2(string s);
You could then assign this delegate to any method with this signature, like so:
Del2 d; // declare the delegate variable
d = DoWork; // set the delegate to refer to the DoWork method
Where the signature of DoWork
is:
public static long DoWork(string name)
Reassigning Delegates
Delegate objects are immutable; that is, the signature they match cannot be changed once set. However, you can point to another method as long as both have the same signature. In this example, you reassign d
to a new delegate object so that d
then invokes the DoMoreWork
method. You can only do this if both DoWork
and DoMoreWork
have the same signature.
Del2 d; // declare the delegate variable
d = DoWork; // set the delegate to refer to the DoWork method
d = DoMoreWork; // reassign the delegate to refer to the DoMoreWork method
Invoking Delegates
Invoking a delegate is fairly straightforward. You simply substitute the name of the delegate variable for the method name. This invokes the Add
method with values 11 and 22, and returns a long result that is assigned to variable sum
:
Del operation; // declare the delegate variable
operation = Add; // set the delegate to refer to the Add method
long sum = operation(11, 22); // invoke the delegate
The following illustrates the creation, instantiation, and invocation of a delegate:
public class MathClass
{
public static long Add(int i, int j) // static
{
return (i + j);
}
public static long Multiply (int i, int j) // static
{
return (i * j);
}
}
class TestMathClass
{
delegate long Del(int i, int j); // declare the delegate type
static void Main()
{
Del operation; // declare the delegate variable
operation = MathClass.Add; // set the delegate to refer to the Add method
long sum = operation(11, 22); // use the delegate to call the Add method
operation = MathClass.Multiply; // change the delegate to refer to the Multiply method
long product = operation(30, 40); // use the delegate to call the Multiply method
System.Console.WriteLine("11 + 22 = " + sum);
System.Console.WriteLine("30 * 40 = " + product);
}
}
Output
11 + 22 = 33
30 * 40 = 1200
A delegate instance must contain an object reference. The previous example gets around this by declaring methods as static, which means there is no need to specify an object reference. If a delegate refers to an instance method, however, the object reference must be given as follows:
Del operation; // declare the delegate variable
MathClass m1 = new MathClass(); // declare the MathClass instance
operation = m1.Add; // set the delegate to refer to the Add method
In this example, Add
and Multiply
are instance methods of MathClass
. If the methods of MathClass
are not declared as static, you invoke them through the delegate by using an instance of the MathClass
, as follows:
public class MathClass
{
public long Add(int i, int j) // not static
{
return (i + j);
}
public long Multiply (int i, int j) // not static
{
return (i * j);
}
}
class TestMathClass
{
delegate long Del(int i, int j); // declare the delegate type
static void Main()
{
Del operation; // declare the delegate variable
MathClass m1 = new MathClass(); // declare the MathClass instance
operation = m1.Add; // set the delegate to refer to the Add method
long sum = operation(11, 22); // use the delegate to call the Add method
operation = m1.Multiply; // change the delegate to refer to the Multiply method
long product = operation(30, 40); // use the delegate to call the Multiply method
System.Console.WriteLine("11 + 22 = " + sum);
System.Console.WriteLine("30 * 40 = " + product);
}
}
Output
This example provides the same output as the previous example in which the methods were declared as static.
11 + 22 = 33
30 * 40 = 1200
Delegates and Events
The .NET Framework also uses delegates extensively for event-handling tasks like a button click event in a Windows or Web application. While event handling in Java is typically done by implementing custom listener classes, C# developers can take advantage of delegates for event handling. An event is declared like a field with a delegate type, except that the keyword event precedes the event declaration. Events are typically declared public, but any accessibility modifier is allowed. The following example shows the declaration of a delegate and event.
// Declare the delegate type:
public delegate void CustomEventHandler(object sender, System.EventArgs e);
// Declare the event variable using the delegate type:
public event CustomEventHandler CustomEvent;
Event delegates are multicast, which means that they can hold references to more than one event handling method. A delegate acts as an event dispatcher for the class that raises the event by maintaining a list of registered event handlers for the event. The following example shows how you can subscribe multiple functions to an event. The class EventClass
contains the delegate, the event, and a method to invoke the event. Note that invoking an event can only be done from within the class that declared the event. The class TestEvents
can then subscribe to the event using the += operator and unsubscribe using the -= operator. When the InvokeEvent
method is called, it fires the event and any functions that have subscribed to the event will fire synchronously as shown in the following example.
public class EventClass
{
// Declare the delegate type:
public delegate void CustomEventHandler(object sender, System.EventArgs e);
// Declare the event variable using the delegate type:
public event CustomEventHandler CustomEvent;
public void InvokeEvent()
{
// Invoke the event from within the class that declared the event:
CustomEvent(this, System.EventArgs.Empty);
}
}
class TestEvents
{
private static void CodeToRun(object sender, System.EventArgs e)
{
System.Console.WriteLine("CodeToRun is executing");
}
private static void MoreCodeToRun(object sender, System.EventArgs e)
{
System.Console.WriteLine("MoreCodeToRun is executing");
}
static void Main()
{
EventClass ec = new EventClass();
ec.CustomEvent += new EventClass.CustomEventHandler(CodeToRun);
ec.CustomEvent += new EventClass.CustomEventHandler(MoreCodeToRun);
System.Console.WriteLine("First Invocation:");
ec.InvokeEvent();
ec.CustomEvent -= new EventClass.CustomEventHandler(MoreCodeToRun);
System.Console.WriteLine("\nSecond Invocation:");
ec.InvokeEvent();
}
}
Output
First Invocation:
CodeToRun is executing
MoreCodeToRun is executing
Second Invocation:
CodeToRun is executing
See Also
Tasks
Concepts
C# Programming Guide
Delegates (C# Programming Guide)
Events (C# Programming Guide)