gmplGenericStatement

Generic Statement Summary

Generic is a terminal, refactoring statement that occurs within Refactor statements. It is used to specify that individual subprogram parameters be implemented as being generic to accept a list of different types.

The attributes of the Generic statement are as follows:

Attribute Description
Identifier This required attribute specifies the symbol, the actual parameter to be made generic. If the containing Refactor statement had a FileFilter specified then this identifier should be specified relative to it; else, it should be specified relative to the root of the symbol table
Types This optional attribute contains a comma-delimited list of the types to be allowed for the generic parameter.
Constraint This optional parameter specifies a constring type that can be applied to the declaration of the generic. The constaint types are as follows:

Constraint Description
struct The type argument must be a value type. Any value type except Nullable can be specified.
class The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.
new The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.
baseclass The type argument must be or derive from the specified base class.
interface The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.
U The type argument supplied for T must be or derive from the


The Generic statement is especially important for dealing with ByRef parameters that have Variant or Object types. Note that this statement is closely related to the Overload and OverGeneric refactoring statements which also deal with these types of parameters.

Consider the following simple VB6 code


Private Sub Form_Load()
   Dim fiveString As String
   Dim fiveNumber As Integer
   MySub False, fiveString
   MySub True, fiveNumber

   Dim sixNumber As Integer
   sixNumber = 1 + fiveNumber
   MsgBox "Five plus one is " & sixNumber

   Dim sixString As String
   sixString = "one plus " & fiveString
  MsgBox "In other words six is equal to " & sixString
End Sub

Private Sub MySub(ByVal i_Param1 As Boolean, ByRef o_Var As Variant)
   If i_Param1 Then
       o_Var = 5
   Else
       o_Var = "five"
   End If
End Sub
Though this is clearly a contrived example such uses of Variant ByRef parameters are quite common in VB6 code. The important thing to note is that the parameter returns either an integer value or a string value to an argument of the corresponding binary type. Using this default translation script


<gmBasic>
   <Storage Action="Create" Identifier="Overload" />
   <Select DevEnv="VS2013" />
   <Select Dialect="csh" />
   <Select BuildFile="local" />
   <Select DeployLocation="C:\Temp" />
   <Select System="..\..\FromIdl" />
   <Compile Project="..\vb6\Project1.vbp" >
   </Compile>
   <Analyse />
   <Output Status="New" Filename="..\csh\Overload.bnd" />
   <Author />
   <Storage Action="Close" />
</gmBasic>
produces this translation


private void Form_Load(object sender, EventArgs e)
{
   string fiveString = "";
   int fiveNumber = 0;
   int refTemp1 = Convert.ToInt32(fiveString);  <=======
   MySub(false,ref refTemp1);
   MySub(true,ref fiveNumber);

   int sixNumber = 0;
   sixNumber = 1 + fiveNumber;
   System.Windows.Forms.MessageBox.Show("Five plus one is " + sixNumber, "", MessageBoxButtons.OK);

   string sixString = "";
   sixString = "one plus " + fiveString;
  System.Windows.Forms.MessageBox.Show("In other words six is equal to " + sixString, "", MessageBoxButtons.OK);
}
private void MySub(bool i_Param1,ref int o_Var)   <======
{
   if (i_Param1)
   {
      o_Var = 5;  <======
   }
   else
   {
      o_Var = Convert.ToInt32("five");
   }
}
Since the tool has no consistent typing in the the arguments being passed to the parameter o_Var, it takes the first assignment, which is an integer assignment, and types the parameter accordingly. The consequences of this is that the string return case will either throw an exception or return zero. Regardless of this, even if a value is returned to the Form_Load method it will end up being stored in the refTemp1 variable and not in fiveString. Using a Refactor.Fixtype statement like the following


<FixType identifier="Project1.Form1.MySub.o_Var" Type="Variant" status="ObjectOnly" />
produces this improved translation of MySub


private void Form_Load(object sender, EventArgs e)
 {
   string fiveString = "";
   int fiveNumber = 0;
   object argTemp1 = fiveString;
   MySub(false,ref argTemp1);
   object argTemp2 = fiveNumber;
   MySub(true,ref argTemp2);

   int sixNumber = 0;
   sixNumber = 1 + fiveNumber;
   System.Windows.Forms.MessageBox.Show("Five plus one is " + sixNumber, "", MessageBoxButtons.OK);

   string sixString = "";
   sixString = "one plus " + fiveString;
   System.Windows.Forms.MessageBox.Show("In other words six is equal to " + sixString, "", MessageBoxButtons.OK);
}
private void MySub(bool i_Param1,ref object o_Var)
{
   if (i_Param1)
   {
      o_Var = 5;
   }
   else
   {
      o_Var = "five";
   }
}
but makes the calling code in Form_Load even worse. Now neither source argument actually ends up receiving its intended value. The Generic statement resolves this problem by treating the parameter as though it had been marked as ObjectOnly and defining the parameter as being generic.


<gmBasic>
   <Storage Action="Create" Identifier="Overload2" />
   <Select DevEnv="VS2013" />
   <Select Dialect="csh" />
   <Select BuildFile="local" />
   <Select DeployLocation="C:\Temp" />
   <Select System="..\..\FromIdl" />
   <Compile Project="..\vb6\Project1.vbp" >
      <Refactor>
         <Generic identifier="Project1.Form1.MySub.o_Var" Types="Integer,String" />
      </Refactor>
   </Compile>
   <Analyse />
   <Output Status="New" Filename="..\csh\Overload2.bnd" />
   <Author />
   <Storage Action="Close" />
 </gmBasic>
In the Generic statement the Identifier attribute contains the fully qualified identifier of the parameter and the Types attribute specifies that there are to be two types of arguments expected in the calls to this method. These types are not actually used in the translated code, but are used to check argument types passed to the parameter.

The resultant translation is as follows.


private void Form_Load(object sender, EventArgs e)
{
   string fiveString = "";
   int fiveNumber = 0;
   MySub(false,ref fiveString);
   MySub(true,ref fiveNumber);

   int sixNumber = 0;
   sixNumber = 1 + fiveNumber;
   System.Windows.Forms.MessageBox.Show("Five plus one is " + sixNumber, "", MessageBoxButtons.OK);

   string sixString = "";
   sixString = "one plus " + fiveString;
   System.Windows.Forms.MessageBox.Show("In other words six is equal to " + sixString, "", MessageBoxButtons.OK);
}
private void MySub<T>(bool i_Param1,ref T o_Var)
{
   if (i_Param1)
   {
      o_Var = (T)(object)5;
   }
   else
   {
      o_Var = (T)(object)"five";
   }
}
In this generic translation the calling code is able to pass its local variables directly ByRef; so those variables will receive their intended values. The actual method code does not force any typing or conversion operations on the variant parameter; but rather uses (object) casts to force the different instance types into the parameter.
Table of Contents