Subscribe

RSS Feed (xml)

Showing posts with label Constraints in C#. Show all posts
Showing posts with label Constraints in C#. Show all posts

Using Constraint to Establish Relationship Between Two Type Parameters

One of the more interesting aspects of the base class constraint is that it allows you to establish a relationship between two type parameters. For example, consider the following generic class declaration:
class Gen<T, V> where V : T {
In this declaration, the where clause tells the compiler that V must inherit T. If this relationship is not present when an object of type Gen is declared, then a compile-time error will result. A constraint that uses a type parameter, such as that just shown, is called a naked type constraint. The following example illustrates this constraint:
// Create relationship between two type parameters.

using System;

class A {
  //...
}

class B : A {
  // ...
}

// Here, V must inherit T.
class Gen<T, V> where V : T {
  // ...
}

class NakedConstraintDemo {
  public static void Main() {

    // This declaration is OK because B inherits A.
    Gen<A, B> x = new Gen<A, B>();

    // This declaration is in error because
    // A does not inherit B.
//    Gen<B, A> y = new Gen<B, A>();

  }
}
First, notice that class B inherits class A. Next, examine the two Gen declarations in Main( ). As the comments explain, the first declaration:
Gen<A, B> x = new Gen<A, B>();
is legal because B inherits A. However, the second declaration:
//    Gen<B, A> y = new Gen<B, A>();
is illegal because A does not inherit B.

Using Multiple Constraints

There can be more than one constraint associated with a parameter. When this is the case, use a comma-separated list of constraints. In this list, the first constraint must be class or struct (if present), or the base class (if one is specified). It is illegal to specify both a class or struct constraint and a base class constraint. Next, must come any interface constraints. The new( ) constraint must be last. For example, this is a valid declaration:
class Gen<T> where T : MyClass, IMyInterface, new() { // ...
In this case, T must be replaced by a type argument that inherits MyClass, implements IMyInterface, and has a parameterless constructor.
When using two or more type parameters, you can specify a constraint for each parameter by using a separate where clause. For example:
// Use multiple where clauses.

using System;

// Gen has two type arguments and both have
// a where clause.
class Gen<T, V> where T : class
                where V : struct {
  T ob1;
  V ob2;

  public Gen(T t, V v) {
    ob1 = t;
    ob2 = v;
  }
}

class MultipleConstraintDemo {
  public static void Main() {
    // This is OK because string is a class and
    // int is a value type.
    Gen<string, int> obj = new Gen<string, int>("test", 11);

    // The next line is wrong because bool is not
    // a reference type.
//    Gen<bool, int> obj = new Gen<bool, int>(true, 11);

  }
}
In this example, Gen takes two type arguments, and both have a where clause. Pay special attention to its declaration:
class Gen<T, V> where T : class
                where V : struct {
Notice that the only thing that separates the first where clause from the second is whitespace. No other punctuation is required or valid.
Technorati :

Reference Type and Value Type Constraints

The last two constraints which explained in the previous posts enable you to indicate that a type argument must be either a reference type or a value type. These constraints are useful in the few cases where the difference between reference and value types is important to generic code. The general forms of the where statements for these constraints are shown here:

where T : class

where T : struct

Here, T is the name of the type parameter. When additional constraints are present, class or struct must be the first constraint in the list.

Here is an example that demonstrates the reference type constraint:

// Demonstrate a reference constraint.

using System;
class MyClass {
  //...
}

// Use a reference constraint.
class Test<T> where T : class {
  T obj;

  public Test() {
    // The following statement is legal only
    // because T is guaranteed to be a reference
    // type, which can be assigned the value null.
    obj = null;
  }

  // ...
}

class ClassConstraintDemo {
  public static void Main() {

    // The following is OK because MyClass is a class.
    Test<MyClass> x = new Test<MyClass>();

    // The next line is in error because int is
    // a value type.
//    Test<int> y = new Test<int>();
  }
}

First, notice how Test is declared:

class Test<T> where T : class {

The class constraint requires that any type argument for T be a reference type. In this program, this is necessary because of what occurs inside the Test constructor:

public Test() {
  // The following statement is legal only
  // because T is guaranteed to be a reference
  // type, which can be assigned the value null.
  obj = null;
}

Here, obj (which is of type T) is assigned the value null. This assignment is valid only for reference types. In C#, you cannot assign null to a value type. Therefore, without the constraint, the assignment would not have been valid, and the compile would have failed. This is one case in which the difference between value types and reference types might be important to a generic routine.

The value type constraint is the complement of the reference type constraint. It simply ensures that any type argument is a value type, including a struct or an enum. Here is an example:

// Demonstrate a value type constraint.

using System;

struct MyStruct {
  //...
}

class MyClass {
  // ...
}

class Test<T> where T : struct {
  T obj;

  public Test(T x) {
    obj = x;
  }

  // ...
}

class ValueConstraintDemo {
  public static void Main() {

    // Both of these declarations are legal.

    Test<MyStruct> x = new Test<MyStruct>(new MyStruct());

    Test<int> y = new Test<int>(10);

    // But, the following declaration is illegal!
//    Test<MyClass> z = new Test<MyClass>(new MyClass());
  }
}

In this program, Test is declared as shown here:

class Test<T> where T : struct {

Because T of Test now has the struct constraint, T can be passed only value type arguments. This means that Test<MyStruct> and Test<int> are valid, but Test<MyClass> is not. To prove this, try removing the comment symbol from the start of the last line in the program and recompiling. An error will be reported.

Technorati :

Using Interface Constraint

The interface constraint enables you to specify an interface that a type argument must implement. The interface constraint serves the same two important purposes as the base class constraint. First, it lets you use the members of the interface within the generic class. Second, it ensures that only type arguments that implement the specified interface are used. This means that for any given interface constraint, the type argument must be either the interface or a class that implements that interface.

The interface constraint uses this form of the where clause:

where T : interface-name

Here, T is the name of the type parameter, and interface-name is the name of the interface. More than one interface can be specified by using a comma-separated list. If a constraint includes both a base class and interface, then the base class must be listed first.

The following program illustrates the interface constraint by reworking the telephone list example shown in the previous section. In this version, the PhoneNumber class has been converted into an interface called IPhoneNumber. This interface is then implemented by Friend and Supplier.

// Use an interface constraint.

using System;

// A custom exception that is thrown if a name or number
// is not found.
class NotFoundException : ApplicationException { }

// An interface that supports a name and phone number.
public interface IPhoneNumber {

  string Number {
    get;
    set;
  }

  string Name {
    get;
    set;
  }
}

// A class of phone numbers for friends.
// It implements IPhoneNumber.
class Friend : IPhoneNumber {
  string name;
  string number;

  bool isWorkNumber;

  public Friend(string n, string num, bool wk) {
    name = n;
    number = num;

    isWorkNumber = wk;
  }

  public bool IsWorkNumber {
    get {
      return isWorkNumber;
    }
  }

  // Implement IPhoneNumber
  public string Number {
    get { return number; }
    set { number = value; }
  }

  public string Name {
    get { return name; }
    set { name = value; }
  }

  // ...
}

// A class of phone numbers for suppliers.
class Supplier : IPhoneNumber {
  string name;
  string number;

  public Supplier(string n, string num) {
    name = n;
    number = num;
  }
  // Implement IPhoneNumber
  public string Number {
    get { return number; }
    set { number = value; }
  }

  public string Name {
    get { return name; }
    set { name = value; }
  }

  // ...
}

// Notice that this class does not implement IPhoneNumber.
class EmailFriend {

  // ...
}

// PhoneList can manage any type of phone list
// as long as it implements IPhoneNumber.
class PhoneList<T> where T : IPhoneNumber {
  T[] phList;
  int end;

  public PhoneList() {
    phList = new T[10];
    end = 0;
  }

  public bool add(T newEntry) {
    if(end == 10) return false;

    phList[end] = newEntry;
    end++;

    return true;
  }

  // Given a name, find and return the phone info.
  public T findByName(string name) {

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

      // Name can be used because it is a member of
      // IPhoneNumber, which is the interface constraint.
      if(phList[i].Name == name)
        return phList[i];

    }

    // Name not in list.
    throw new NotFoundException();
  }
  // Given a number, find and return the phone info.
  public T findByNumber(string number) {

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

      // Number can be used because it is also a member of
      // IPhoneNumber, which is the interface constraint.
      if(phList[i].Number == number)
        return phList[i];
    }

    // Number not in list.
    throw new NotFoundException();
  }

  // ...
}

// Demonstrate interface constraints.
class UseInterfaceConstraint {
  public static void Main() {

    // The following code is OK because Friend
    // implements IPhoneNumber.
    PhoneList<Friend> plist = new PhoneList<Friend>();
    plist.add(new Friend("Tom", "555-1234", true));
    plist.add(new Friend("Gary", "555-6756", true));
    plist.add(new Friend("Matt", "555-9254", false));

    try {
      // Find the number of a friend given a name.
      Friend frnd = plist.findByName("Gary");

      Console.Write(frnd.Name + ": " + frnd.Number);

      if(frnd.IsWorkNumber)
        Console.WriteLine(" (work)");
      else
        Console.WriteLine();
    } catch(NotFoundException) {
      Console.WriteLine("Not Found");
    }

    Console.WriteLine();

    // The following code is also OK because Supplier
    // implements IPhoneNumber.
    PhoneList<Supplier> plist2 = new PhoneList<Supplier>();
    plist2.add(new Supplier("Global Hardware", "555-8834"));
    plist2.add(new Supplier("Computer Warehouse", "555-9256"));
    plist2.add(new Supplier("NetworkCity", "555-2564"));

    try {
      // Find the name of a supplier given a number
      Supplier sp = plist2.findByNumber("555-2564");
      Console.WriteLine(sp.Name + ": " + sp.Number);
    } catch(NotFoundException) {
        Console.WriteLine("Not Found");
    }

    // The following declaration is invalid
    // because EmailFriend does NOT implement IPhoneNumber.
//    PhoneList<EmailFriend> plist3 =
//        new PhoneList<EmailFriend>(); // Error!
  }
}

Using the new( ) Constructor Constraint

The new( ) constructor constraint enables you to instantiate an object of a generic type. Normally, you cannot create an instance of a generic type parameter. However, the new( ) constraint changes this because it requires that a type argument supply a parameterless constructor. (This parameterless constructor can be the default constructor provided automatically when no explicit constructor is declared.) With the new( ) constraint in place, you can invoke the parameterless constructor to create an object.

Here is a simple example that illustrates the use of new( ):

// Demonstrate a new() constructor constraint.

using System;

class MyClass {

  public MyClass() {
    // ...
  }

  //...
}

class Test<T> where T : new() {
  T obj;

  public Test() {
    // This works because of the new() constraint.
    obj = new T(); // create a T object
  }

  // ...
}

class ConsConstraintDemo {
  public static void Main() {

    Test<MyClass> x = new Test<MyClass>();

  }
}

First, notice the declaration of the Test class, shown here:

class Test<T> where T : new() {

Because of the new( ) constraint, any type argument must supply a parameterless constructor. As explained, this can be the default constructor or one that you create.

Next, examine the Test constructor, shown here:

public Test() {
  // This works because of the new() constraint.
  obj = new T(); // create a T object
}

A new object of type T is created, and a reference to it is assigned to obj. This statement is valid only because the new( ) constraint ensures that a constructor will be available. To prove this, try removing the new( ) constraint, and then attempt to recompile the program. As you will see, an error will be reported.

In Main( ), an object of type Test is instantiated, as shown here:

Test<MyClass> x = new Test<MyClass>();

Notice that the type argument is MyClass and that MyClass defines a parameterless constructor. Thus, it is valid for use as a type argument for Test. It must be pointed out that it was not necessary for MyClass to explicitly declare a parameterless constructor. Its default constructor would also satisfy the constraint. However, if a class needs other constructors in addition to a parameterless one, then it would be necessary to also explicitly declare a parameterless version.

Two important points about using new( ): First, it can be used with other constraints, but it must be the last constraint in the list. Second, new( ) allows you to construct an object using only the parameterless constructor, even when other constructors are available. In other words, it is not permissible to pass arguments to the constructor of a type parameter.

Technorati :

LocalsAdda.com-Variety In Web World

Fun Mail - Fun in the Mail