User-defined generics have restrictions in the following areas:

  • .NET objects
  • CATCH statement
  • ENUM statement
  • TYPE-OF function
  • CAST function
  • GET-CLASS function
  • NEW statement
  • STATIC members

The following sections describe these restrictions.

.NET objects

.NET objects cannot be used as type parameters or type arguments. For example:

CLASS SimpleGenericType<T as System.Object>. //invalid

CATCH statement

The CATCH statement does not allow a generic type or a type parameter. For example, the following CATCH statements produce compiler errors:

CATCH appErr AS myAppError<Dog>: //compiler error
...

CATCH appErr AS T: //compiler error
...

ENUM statement

ENUM types cannot be generic.

TYPE-OF and CAST functions

Within a generic class implementation, you cannot use a type parameter by itself where a type is required by the TYPE-OF() or CAST() built-in functions. For example:

CLASS MyGeneric<T AS Animal>: 
  METHOD VOID makeNoise(myPLO AS Progress.Lang.Object):      
    IF TYPE-OF(myPLO, T) //compiler error: T is a type parameter
    THEN
      CAST(myPLO, T):Sound(). //compiler error
  END METHOD.
END CLASS. 

In these cases, use the constraint type, instead of T. For example:

CLASS MyGeneric <T AS Animal>: 
  METHOD VOID makeNoise(myPLO AS Progress.Lang.Object): 
      IF TYPE-OF(myPLO, Animal) THEN
         CAST(myPLO, Animal):Sound().
  END METHOD. 
END CLASS.

GET-CLASS function

Within a generic class implementation, you cannot use a type parameter by itself where a type is required by the GET-CLASS() built-in function. For example:
VAR Progress.Lang.Class myCls = GET-CLASS(T). //compiler error

NEW statement

The NEW statement does not allow a type parameter to be used directly. The compiler cannot validate the constructor signature because the actual type is not known until the client of the generic class supplies a type argument. For example:

VAR T newInstance = NEW T(). //compiler error
  
VAR T newInstance2 = NEW T(10). //compiler error

You can use dynamic programming or reflection for tasks that depend on the type argument supplied by the client of the generic type. The Progress.Lang.Class type provides the GetTypeArguments() method, which returns the types of the type arguments. For example:

CLASS myList<T AS Progress.Lang.Object>:
  DEFINE PRIVATE VARIABLE arg1plc AS CLASS Progress.Lang.Class.
  CONSTRUCTOR myList():
    VAR Progress.Lang.Class[] myArgs.
    myArgs = THIS-OBJECT:GetClass():GetTypeArguments().
    arg1plc = myArgs[1]. //store the type of the 1st Type Argument
  END. 
  METHOD T makeNewObj():
    /* dynamically new an object whose type is the Type Argument supplied */
    RETURN arg1plc:new().
  END METHOD.
END CLASS.
This restriction does not apply when a type parameter is used as a type argument for another generic type and the type constraints are compatible. In that case, the type parameter is passed through to the other generic type. In the following example, assuming that the type constraint for myListIterator is Progress.Lang.Object, the NEW statement is allowed because the constraint for T is Progress.Lang.Object:
CLASS myList<T AS Progress.Lang.Object>: 
  DEFINE PUBLIC PROPERTY Iterator AS myIIterator<T> NO-UNDO 
    Get(): 
      RETURN NEW myListIterator<T>(THIS-OBJECT). //valid 
    END. 
END.

The following, more complex hierarchical example also compiles:

CLASS myBaseClass:
END.

CLASS myDerivedClass INHERITS myBaseClass:
END.

CLASS mySimpleGeneric<T as myBaseClass>: 
END.

CLASS DerivedGeneric<Y AS myDerivedClass>: 
  METHOD PUBLIC VOID somemeth(elem AS Y):
    VAR mySimpleGeneric<Y> simpleGen;
  END.
END.

In this example, mySimpleGeneric has a type parameter constrained to myBaseClass. Because the type parameter in DerivedGeneric is constrained to myDerivedClass, the variable declaration in somemeth is valid: myDerivedClass is a subclass of myBaseClass. If the constraints are incompatible, the compiler raises an error.

In summary, a type parameter can be used with TYPE-OF, CAST, GET-CLASS, and NEW only when it is passed as a type argument to another generic type and follows the type-compatibility rules.

STATIC members

Generic classes cannot define STATIC members including methods, variables, properties, and events. Static members can be defined in a non-generic class in the inheritance hierarchy, but not in the generic class itself.

You can access an inherited static member through the generic type using the TYPE-NAME syntax. but understand that they are in the non-generic class. However, because the member is defined by the non-generic class, referencing that class directly is clearer. For example, given these classes:

CLASS myroot:
  DEFINE PUBLIC STATIC PROPERTY myprop AS INT GET. SET.
END.

CLASS SimpleGeneric<T AS Progress.Lang.Object> INHERITS myroot.
END.

Both statements are valid, but the second is clearer:

myvar = SimpleGeneric<Employee>:myprop.

myvar = myroot:myprop.