Free!  FoxPro to WPF screen converter
   Almost free!  One day of FREE VFP to WPF evaluation of your FoxPro app ONSITE
   Free!  Read the foreword to Les' new book (available here)...
Skip Navigation Links

Macro Expansion in .NET

Microsoft has a different approach to FoxPro's macro expansion; it's called Reflection


Les Pinter

Login or register to download source code

Introduction

FoxPro has many features that make it easy to build applications. One that we've come to rely on heavily is macro expansion, which looks like this (Most developers leave off the ending period, so you would just see ? &VarName without the period):

VarName = IIF ( m.Current, [CurBal], [OldBal] ) 
? &Varname.

&VarName is evaluated as "The variable whose name is stored in VarName". The value of either CurBal or OldBal would be printed.

This poses a challenge in .NET languages, because macro expansion "reflects" the open nature of FoxPro: The compiler is available at runtime. It's not in Visual Basic, C#, or any other .NET language. But there is a way to do most of what we use macro expansion for: It's called reflection. This article shows how to use it.

Finding the value of a variable by name

If a variable is declared globally, we can find it using the name:

Global variable class

C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GlobalVars
{
    class vars
    {

        public static string emp_code = "502014";
        public static string CompCode = "001";
        public static int BranchID = 1;
        public static int privilege = 2;
        public static float Var1 = 6.02F;        

    }
}

VB:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text

Public Class vars

    Public Shared String emp_code = "502014
    Public Shared String CompCode = "001"
    Public Shared Int BranchID = 1
    Public Shared Int privilege = 2
    Public Shared Floag Var1 = 6.02F

End Class

Once you've created this class, you can read the values of its fields (that's what variables in a class's Declarations are called in) like this:

Returning the value of a variable using its name

C#:

// In Declarations:
    vars variables = new vars();
    ...
// In your code
    string varval = read_variable(VarName);
    ...
//  Function to return a string
    private string read_variable(string var1)
    {
      FieldInfo myf = typeof(vars).GetField(var1);
      return myf.GetValue(null).ToString();
    }
    
VB:

' In Declarations:
    Dim variables As vars = new vars()
    ...
' In your code
    Dim varval As string = read_variable(VarName)
    ...
'  Function to return a string
    Function read_variable(string var1) As String

      Dim myf As FieldInfo = typeof(vars).GetField(var1)
      return myf.GetValue(null).ToString()

You can also store the value, although a little code to ensure that the data is compatible with the target type would probably be a good idea. That's the type safety that the IDE gives you as a corollary of IntelliSense.)

You can also use reflection to return type information about your fields:

C#:
Type t = variables.GetType();
FieldInfo[] fi = t.GetFields();
foreach (FieldInfo field in fi)
  {
    FieldInfo myf = typeof(vars).GetField(field.Name);
    debug.print(field.Name + "\t" + field.FieldType.Name + "\t" + myf.GetValue(null));
  }

VB:
Dim t as Type = variables.GetType()
Dim fi() as FieldInfo= t.GetFields();
For Each field as FieldInfo field in fi
    Dim myf as FieldInfo = typeof(vars).GetField(field.Name)
    debug.print(field.Name + "\t" + field.FieldType.Name + "\t" + myf.GetValue())
End For

Since the variable you want to return might be a string, an integer, or any other type, you need several overloads of this function:

Overloads of store_variable

private string store_variable(string var1, string NewValue)
{
  string message = "";
  FieldInfo myf = typeof(vars).GetField(var1);
  switch (myf.FieldType.Name)
  {
    case "Int32":
      if (NewValue.Length > 0 && !System.Text.RegularExpressions.Regex.IsMatch(NewValue, "[^0-9]"))
      {
        myf.SetValue(null, int.Parse(Validate_format(NewValue)));
        message = "New value saved";
      }
      else message = "Can't convert String to Int32"; break;
    case "Single":
      if (NewValue.Length > 0 && !System.Text.RegularExpressions.Regex.IsMatch(NewValue, "[^0-9]"))
      {
        myf.SetValue(null, System.Convert.ToSingle(Validate_format2(NewValue)));
        message = "New value saved";
      }
      else message = "Can't convert String to Single";break;
    case "String":
      myf.SetValue(null, System.Convert.ToString(NewValue));
      message = "New value saved";break;
  }
  return message;
}

The sample application to accompany this article, available to registered users (no charge, of course), looks like this:



Fig. 1 - Demonstration of reflection

Macro substitution in the case of column names

When the expression that a macro substitution refers to is a column name in the current table, it's also easy; this is because the syntax of the DataRow() structure uses the column's name, e.g.

DataRow columns

C#:
string VarName = "CustID";

// These two statements produce the same result:
debug.print ( ds.Tables[0].Rows[0]["CustID"] );
 -or-
debug.print ( ds.Tables[0].Rows[0][VarName]  );

VB:
Dim VarName As String = "CustID"

// These two statements produce the same result:
debug.print ( ds.Tables(0).Rows(0)("CustID") )
 -or-
debug.print ( ds.Tables(0).Rows(0)(VarName)  )

Where it gets harder

If your FoxPro expression is an executable line of code, however, you're looking at a whole other animal. Consider this code fragment:

Var1 = IIF ( InReceivership, "Lawyer", "APContact" )
Var2 = IIF ( InReceivership, "LawyerName", "ContactName" )
Expr = Var1 + " = "+ Var2
&Expr

you've just executed a statement that either assigns the value of ContactName to the variable APContact, or LawyerName to the variable Lawyer. This simple assignment, which is trivial in FoxPro (except for the fact that it might blow up at runtime), raises so many issues internally that it's just not amenable to a simple translation. And more complex statements, like this one

Cmd = "REPORT FORM " + ReportName + PreviewOption + RestOption + ForOption
&Cmd

which might expand to this:

REPORT FORM Rpt2001 PREVIEW REST FOR State = [CO]

don't have a simple equivalent in .NET - at least, not as a one-line statement.

The good news is: SQL

The good news is that many of the programs we see use macro substitution to construct and execute SQL statements, and that much, at least, works almost exactly the same in both C# and VB as it does in FoxPro. This code fragment:

FoxPro SELECT statement executed using Macro Expansion:

STORE "" TO Exprs, Expr1, Expr2
IF NOT EMPTY ( Val1 )
   Expr1 = " AND " + ALLTRIM(Var1) + " = '" + Val1 + "'"
ENDIF
IF NOT EMPTY ( Val2 )
   Expr2 = " AND " + ALLTRIM(Var2) + " = '" + Val2 + "'"
ENDIF
Cmd = "SELECT " + FieldList + " FROM "  + TableName
IF NOT EMPTY ( Expr1 )
   Exprs = Exprs + Expr1
ENDIF
IF NOT EMPTY ( Expr2 )
   Exprs = Exprs + Expr2
ENDIF
IF NOT EMPTY ( Exprs )
   Cmd = Cmd + SUBSTR(Exprs,5)
ENDIF

Cmd = Cmd + " INTO CURSOR C1"
&Cmd

IF RECCOUNT([C1]) <> 0
   ...
ENDIF

In .NET, it's very similar:

SELECT statement construction:

VB:
Dim Exprs As String = "": Dim Expr1 As String = "": Dim Expr2 As String = ""
Expr1 = Iif ( Var1 = "", "", " AND " & Var1.TrimEnd() & " = '" & Val1.TrimEnd() & "'"
Expr1 = Iif ( Var1 = "", "", " AND " & Var1.TrimEnd() & " = '" & Val1.TrimEnd() & "'"
Cmd = "SELECT " & FieldList & " FROM "  & TableName    ' vars declared earlier
IF Expr1 <> "" Then Exprs = Exprs + Expr1
IF Expr2 <> "" Then Exprs = Exprs + Expr2
IF Exprs <> "" Then Cmd = Cmd + Exprs.SubString(5)
Dim ds As New Dataset
Dim da As New SQLDataAdapter ( Cmd, cn )
da.fill(ds, TableName)
If ds.Tables(0).Rows.Count > 0 Then
   ...
End If

C#:
string Exprs = ""; string Expr1 = ""; string Expr2 = "";
Expr1 = Var1? "", " AND " + Var1.TrimEnd() + " = '" & Val1.TrimEnd() + "'";
Expr1 = Iif ( Var1 = "", "", " AND " & Var1.TrimEnd() & " = '" & Val1.TrimEnd() & "'"
string Cmd = "SELECT " + FieldList + " FROM "  + TableName   // vars declared earlier
IF (Expr1 != "") { Exprs = Exprs + Expr1; }
IF (Expr2 != "") { Exprs = Exprs + Expr2; }
IF (Exprs != "") { Cmd = Cmd + Exprs.SubString(5); }
Dataset ds = New Dataset();
SQLDataAdapter da = New SQLDataAdapter ( Cmd, cn );
da.fill(ds, TableName);
If ( ds.Tables(0).Rows.Count > 0 )
 { ... }

Conclusion

The simpler forms of macro expansion are relatively easy in .NET languages; however, for code blocks, or for more complex expressions, you'll just have to rewrite the code.

We've lost jobs due to this, because our clients were old-school FoxPro developers who were unfamiliar with .NET, and just couldn't believe that the newer .NET languages didn't have exact equivalents for all of FoxPro's capabilities, including macro expansion.

Read my lips: Nothing runs like a Fox.

qqq


Copyright(C) Pinter Consulting, 2012Tel: +1 (650) 464-6924
Automated conversion between C# and VB by Visible C#/Visible VB from Tangible Software Solutions