Programming Office Applications Using Microsoft Visual C#

 

Ken Getz
MCW Technologies, LLC

June 2003

Applies to:
    Microsoft® Visual Studio® Tools for the Microsoft Office System
    Microsoft Office Word 2003
    Microsoft Office Excel 2003
    Microsoft Visual C#® .NET 2003
    Microsoft Visual Basic® .NET 2003
    Microsoft Visual Basic® for Applications (VBA)
    Microsoft Visual Studio® .NET 2003

Summary: Presents some of the issues that C# developers must be aware of when using Visual Studio Tools for the Microsoft Office System to create Word and Excel applications. Discusses how to work with the VBA-focused methods and properties for Word and Excel, and provides examples comparing Visual Basic .NET code with the corresponding C# code. (18 pages)

Contents

Introduction
Passing Parameters to Microsoft Office Word 2003
Handling Optional Parameters with Microsoft Office Excel 2003
Handling Optional Parameters with Microsoft Office Word 2003
Handling Parameterized Properties with Excel
Using Accessor Methods in Word
Late Binding in Word
Summary

Introduction

Microsoft® Visual Studio Tools for the Microsoft Office System makes it possible for you to create rich Microsoft Office 2003-based applications in Microsoft Visual Studio® .NET, using the power of Microsoft Office Word 2003 and Microsoft Office Excel 2003. You can take advantage of all the functionality provided by the large object models exposed by these products from either Microsoft Visual Basic® .NET or Microsoft Visual C#®. Although Visual Basic .NET developers can work comfortably with the objects exposed by the Office products, C# developers face unique challenges. Because of the history and original design of the Office object models, the members of their various objects were created with the intent that they'd be called by Visual Basic for Applications (VBA) code. As such, the various properties and methods take advantage of features provided by VBA that don't correspond to features of C#. This document points out most of the issues involved in working with VBA-focused methods and properties for both Word and Excel, and shows examples comparing Visual Basic .NET code and the corresponding C# code.

When using Visual Studio Tools for the Microsoft Office System, C# developers must be aware of issues that don't even enter the consciousness of VBA or Visual Basic .NET developers. In particular, the following list describes the issues discussed in detail in this document:

  • Passing parameters: By default, VBA expects parameters to be passed by reference. By default, Visual Basic .NET and C# both pass parameters by value. VBA and Visual Basic .NET are fuzzy on this distinction, and the Excel primary interop assembly (PIA) has been created so that it accepts parameters passed by value, even though it originally received all its values from VBA code by reference. The Word PIA is not so accommodating, and all its parameters (for the most part) must be passed by reference. Visual Basic .NET can handle this without extra work, but C# cannot.
  • Optional parameters: VBA and Visual Basic .NET support optional parameters on method calls, but C# does not. Many Office methods allow twenty, thirty, or more optional parameters, causing C# developers to write more code than Visual Basic .NET developers to get the same effect.
  • Parameterized properties: VBA and Visual Basic .NET support properties that accept parameters, acting as read-only functions. C# does not, requiring accessor methods for setting and retrieving property values that accept parameters. The same issues apply for Word when using methods that accept Variant parameters.
  • Late binding: VBA can determine properties of objects at run time, effectively providing late binding between your code and the objects themselves. Microsoft Office Word takes advantage of this behavior in its Dialog class. Visual Basic .NET can only handle late binding with its Option Strict Off directive (the default behavior for Visual Basic .NET). C# developers (and Visual Basic .NET developers using Option Strict On) cannot take advantage of late binding and must use alternative means to programmatically interact with the Word Dialog class.

In order to expose the Office object model functionality for Visual Basic .NET and C# developers, Microsoft created a set of primary interop assemblies (PIAs). Visual Studio Tools for the Microsoft Office System uses individual PIAs for Word and Excel, and each has its own distinct behaviors for both Visual Basic .NET and C# developers. The remainder of this document discusses each of these issues, showing how the Word and Excel PIAs handle each case, with code samples demonstrating how C# developers can handle each of the situations described here.

Passing Parameters to Microsoft Office Word 2003

Objects provided by Microsoft Office Word expect parameters to be passed by reference. Although this isn't a problem for Visual Basic .NET developers, C# developers must take extra care when passing parameters to Word methods. Each of the methods provided by Word expects each of its methods to be passed using the ref ** keyword, and you can only pass lvalues ** (that is, elements that can appear on the left-hand side of an assignment) by reference. You won't be able to pass literal values to Word methods from C# code.

As a simple example, the following procedure creates a new window and then tiles the two windows. The code fragment demonstrates both the Visual Basic .NET and C# versions of the code:

' Visual Basic
Friend Sub CreateNewWindowAndTile()
    ' Create a new window from the active document.
    Dim wnd As Word.Window = _
        ThisApplication.ActiveWindow.NewWindow
    ' Tile the two windows.
    ThisApplication.Windows.Arrange( _
        Word.WdArrangeStyle.wdTiled)
End Sub

// C#
public void CreateNewWindowAndTile() 
{
    // Create a new window from the active document.
    Word.Window wnd =  ThisApplication.ActiveWindow.NewWindow();

    // Tile the two windows.
    Object value = Word.WdArrangeStyle.wdTiled;
    ThisApplication.Windows.Arrange(ref value);
}

In this example, the code creates an Object variable and assigns it a value. The code then passes this variable, using the ref ** keyword, to the Arrange method. You must call every method in Word this same way—each parameter must be passed using the ref ** keyword, and must pass a variable containing the actual value.

**Note   **C# developers targeting Microsoft Office Excel don't need to take any special steps. The Microsoft Office Excel 2003 PIA has been created so that literal values passed by value to the various members are handled correctly.

Handling Optional Parameters with Microsoft Office Excel 2003

As in other languages that support optional parameters, optional parameters in VBA must appear at the end of the method's invocation list, and once a parameter is marked as optional, the remainder of the parameters must also be optional. Many methods provided by Excel accept optional parameters, and Visual Basic .NET developers can simply disregard these parameters to accept their default values.

For example, the Workbooks.Add method allows you to specify an optional parameter indicating the template you'd like to use. You can specify either the name of a template file, a member of the XlWBATemplate constants indicating the type of sheet you'd like your new workbook to contain, or supply no value (creating a workbook with blank worksheets). If you want to supply no value (accepting the default behavior), Visual Basic .NET developers can write code like the following:

' Visual Basic
Dim wb As Excel.Workbook = ThisApplication.Workbooks.Add()

Similar code in C# won't compile, however, because of the missing parameter. To solve this problem, pass the Type.Missing field for the optional parameter:

// C#
Excel.Workbook wb = ThisApplication.Workbooks.Add(Type.Missing);

Type.Missing is useful to Visual Basic .NET developers when using reflection to dynamically create method calls to methods that accept optional parameters. It indicates that you want to use the default value for the specified parameter. You could, of course, peruse the Excel documentation, find the default value yourself, and pass it explicitly.

If the Excel method requires multiple optional parameters, you may find that writing code in C# requires more effort than writing the corresponding Visual Basic .NET code. For example, the following fragments compare code in Visual Basic .NET and C#. The Workbooks.Open method allows you to specify quite a few optional parameters, indicating the behavior of the workbook you're opening:

' Visual Basic
Dim wb As Excel.Workbook = _
    ThisApplication.Workbooks.Open("C:\YourPath\YourWorkbook.xls")

// C#
Excel.Workbook wb = ThisApplication.Workbooks.Open( 
    "C:\\YourPath\\Yourworkbook.xls", 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing);

You can only pass the Type.Missing value for parameters that accept reference types. For value-type parameters, you'll need to determine the actual default value, and pass that instead. The Range.Sort method, for example, accepts a large number of enumerated values as parameters—being value types, these parameters required you to specify exactly the value you want to pass:

' Visual Basic
' rng is a variable of type Excel.Range.
rng.Sort(rng, _
    Orientation:=Excel.XlSortOrientation.xlSortColumns)

// C#
// rng is a variable of type Excel.Range.
rng.Sort(rng, Excel.XlSortOrder.xlAscending, 
    Type.Missing, Type.Missing, Excel.XlSortOrder.xlAscending, 
    Type.Missing, Excel.XlSortOrder.xlAscending, 
    Excel.XlYesNoGuess.xlNo, Type.Missing,Type.Missing, 
    Excel.XlSortOrientation.xlSortColumns, 
    Excel.XlSortMethod.xlPinYin, 
    Excel.XlSortDataOption.xlSortNormal, 
    Excel.XlSortDataOption.xlSortNormal, 
    Excel.XlSortDataOption.xlSortNormal);

You may find yourself wondering why C# doesn't include support for optional parameters. Instead, C# and Visual Basic .NET both support method overloading, which allows developers to create multiple "versions" of a single procedure, passing in different sets of parameters. The main restriction on overloaded methods is that the parameter signature of each individual method must be different—the compiler must be able to determine which version of the procedure you're calling, based only on the parameters you call it with. This feature is far more powerful, and more flexible, than is VBA support for optional parameters.

Visual Basic .NET includes optional parameters because Visual Basic and VBA have included this feature for several versions, but code written "fresh" at this point would generally benefit from using overloading rather than optional parameters. In order to promote the best code possible, the C# designers stuck with overloading rather than supporting optional parameters. In general coding, you would never even notice this feature missing from C#. The lack of optional parameters in C# only turns up when programming against object models like those provided by Office, in which many members take advantage of optional parameters, and use a large number of optional parameters.

In some cases, you may find it useful to create wrapper methods around the built-in methods. For example, the Excel WorksheetFunction class exposes a large set of functions originally meant for use on worksheets, providing support for statistical, financial, and other types of worksheet-related functionality. Most of the methods of the WorksheetFunction class accept thirty parameters, with one or two required parameters. Calling these methods from C# becomes terribly onerous, as in the following example, which calls the WorksheetFunction.Min method on a specified Excel Range object:

' Visual Basic
MessageBox.Show(ThisApplication.WorksheetFunction.Min(rng))
MessageBox.Show(ThisApplication.WorksheetFunction.Max(rng))

// C#
MessageBox.Show(ThisApplication.WorksheetFunction.Min(rng, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing).ToString());
MessageBox.Show(ThisApplication.WorksheetFunction.Max(rng, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, 
    Type.Missing).ToString());

For cases like these, you might consider creating a wrapper method that can call the existing method, saving you from counting and keeping track of all the optional parameters.

// C#
private delegate double WorksheetFunctionDelegate(
    object value1, object value2, object value3,
    object value4, object value5, object value6,
    object value7, object value8, object value9,
    object value10, object value11, object value12,
    object value13, object value14, object value15,
    object value16, object value17, object value18,
    object value19, object value20, object value21,
    object value22, object value23, object value24,
    object value25, object value26, object value27,
    object value28, object value29, object value30);

private double CallWorksheetFunction(
    WorksheetFunctionDelegate func,
    Object value,
    params object[] values)
    {
    // All the values except the first one.
    object[] overflowParameters = new object[29];

    values.CopyTo(overflowParameters, 0);
    for (int i = values.Length;  i < overflowParameters.Length;  i++)
    {
        overflowParameters[i] = Type.Missing;
    }

    return func (
        value,
        overflowParameters[0], overflowParameters[1], 
        overflowParameters[2], overflowParameters[3], 
        overflowParameters[4], overflowParameters[5],
        overflowParameters[6], overflowParameters[7], 
        overflowParameters[8], overflowParameters[9], 
        overflowParameters[10], overflowParameters[11],
        overflowParameters[12], overflowParameters[13], 
        overflowParameters[14], overflowParameters[15], 
        overflowParameters[16], overflowParameters[17],
        overflowParameters[18], overflowParameters[19], 
        overflowParameters[20], overflowParameters[21], 
        overflowParameters[22], overflowParameters[23],
        overflowParameters[24], overflowParameters[25], 
        overflowParameters[26], overflowParameters[27], 
        overflowParameters[28]);
    }

Then, to call a worksheet function that accepts a single required parameter, you might write code like the following:

SingleItemWSF func = 
    new WorksheetFunctionDelegate(
    ThisApplication.WorksheetFunction.Min);
MessageBox.Show( 
    CallWorksheetFunction(func, rng, Type.Missing).ToString());

You could expand this functionality to support multiple required parameters, and you could take advantage of the C# params ** keyword, as well.

**Tip   **The samples included with the documentation for Visual Studio Tools for the Microsoft Office System include helper methods for some commonly used members. Within the samples, look for regions or classes with the word "helper" in the title. You can use these methods as the beginning of your own set of methods that make it easier to call methods provided by Word and Excel.

Handling Optional Parameters with Microsoft Office Word 2003

Working with optional parameters in Word is similar to the same functionality in Excel. You might find yourself writing code as in the following fragment which compares opening a Word document in Visual Basic .NET with the same code in C#:

' Visual Basic
ThisApplication.Documents.Open("C:\Test\MyNewDocument")

// C#
Object filename = @"C:\Test\MyNewDocument";
Object confirmConversions = Type.Missing;
Object readOnly = Type.Missing;
Object addToRecentFiles = Type.Missing;
Object passwordDocument = Type.Missing;
Object passwordTemplate = Type.Missing;
Object revert = Type.Missing;
Object writePasswordDocument = Type.Missing;
Object writePasswordTemplate = Type.Missing;
Object format = Type.Missing;
Object encoding = Type.Missing;
Object visible = Type.Missing;
Object openConflictDocument = Type.Missing;
Object openAndRepair  = Type.Missing;
Object documentDirection = Type.Missing;
Object noEncodingDialog = Type.Missing;

ThisApplication.Documents.Open(ref filename, 
    ref confirmConversions, ref readOnly, ref addToRecentFiles, 
    ref passwordDocument, ref passwordTemplate, ref revert, 
    ref writePasswordDocument, ref writePasswordTemplate, 
    ref format, ref encoding, ref visible, ref openConflictDocument, 
    ref openAndRepair , ref documentDirection, ref noEncodingDialog);

Calling Word methods from C#, in general, is more complex than calling similar Excel methods, because you must pass all the parameters by reference. You'll find it useful, if you need to call methods like this one often, to create a helper procedure that allows you to pass in only the necessary parameters. Your helper procedure can then pass the Type.Missing value for all the other parameters. For example, the Find method in Word allows Visual Basic .NET developers to pass in a large number of optional parameters—for C# developers, this means a lot of typing. You could create methods like the following, which allow you to pass in either a Word.Find object, or a Word.Find object and a few other parameters:

// C#
private Boolean ExecuteFind(Word.Find find)
{
    return ExecuteFind(find, Type.Missing, Type.Missing);
}

private Boolean ExecuteFind(
    Word.Find find, Object wrapFind, Object forwardFind)
{
    // Simple wrapper around Find.Execute:
    Object findText = Type.Missing;
    Object matchCase = Type.Missing;
    Object matchWholeWord = Type.Missing;
    Object matchWildcards = Type.Missing;
    Object matchSoundsLike = Type.Missing;
    Object matchAllWordForms = Type.Missing;
    Object forward = forwardFind;
    Object wrap = wrapFind;
    Object format = Type.Missing;
    Object replaceWith = Type.Missing;
    Object replace = Type.Missing;
    Object matchKashida = Type.Missing;
    Object matchDiacritics = Type.Missing;
    Object matchAlefHamza = Type.Missing;
    Object matchControl = Type.Missing;
    
    return find.Execute(ref findText, ref matchCase, 
        ref matchWholeWord, ref matchWildcards, ref matchSoundsLike, 
        ref matchAllWordForms, ref forward, ref wrap, ref format, 
        ref replaceWith, ref replace, ref matchKashida, 
        ref matchDiacritics, ref matchAlefHamza, ref matchControl);
}

Then, to call the ExecuteFind method, you could write code like the following:

// C#
// Use Find properties to specify search criteria.
Word.Find fnd = ThisApplication.Selection.Find;

fnd.ClearFormatting();
fnd.Forward = true;
fnd.Wrap = Word.WdFindWrap.wdFindContinue;
fnd.Text = "ipsum";

ExecuteFind(fnd);

// Use Execute method arguments to specify search criteria.
fnd = ThisApplication.Selection.Find;
fnd.ClearFormatting();

Object findText = "dolor";
Object wrap = Word.WdFindWrap.wdFindContinue;
Object forward = true;
ExecuteFind(fnd, wrap, forward);

You'll often need to refer to ranges within Word. In Visual Basic .NET, specifying the Range property without any parameters returns the entire range—you needn't specify the start and end values if you simply want to work with the entire contents of the document. This doesn't hold true for C# developers, of course, because you must always pass values for all the optional parameters. In C#, you can pass Type.Missing for all optional parameters to assume the default values, as in the following fragment:

' Visual Basic
Dim rng As Word.Range = ThisDocument.Range()
rng.Select()

// C#
Object start = Type.Missing;
Object end = Type.Missing;

Word.Range rng = ThisDocument.Range(ref start, ref end);
rng.Select();

Handling Parameterized Properties with Excel

Many properties in Excel allow you to pass parameters, indicating the specific instance of the property with which you'd like to interact. For example, you can specify a Range object by passing a named range as a string. You can also specify a range by passing the upper-left and lower-right cell names. You must specify at least one parameter (a named range), but you can specify two (the two cell names). In each case, the property returns an Excel.Range object corresponding to the specified range.

In Visual Basic .NET, you can write code like the following to specify and retrieve a reference to a Range object:

' Visual Basic
Dim rng1 As Excel.Range = ThisApplication.Range("TotalSales")
' or
ThisApplication.Range("A1", "B2").Font.Bold = True

C# doesn't allow this type of property reference. To work around this limitation, the Excel PIA provides accessor get/set methods for each parameterized property. For instance, to retrieve a reference to an Excel Range using C#, you could rewrite the previous code:

// C#
Excel.Range rng1 = 
    ThisApplication.get_Range("TotalSales", Type.Missing);
// or
ThisApplication.get_Range("A1", "B2").Font.Bold = true;

(Note that you must pass two parameters to the get_Range method, because the Application.Range property in Excel requires one parameter with an optional second parameter.)

The same issues apply to the Offset property of a Range—this parameterized property allows you to retrieve a range a specified number of rows and/or columns from the upper-left corner of the range. The following fragments demonstrate retrieving the property in both Visual Basic .NET and C#:

' Visual Basic
Private Sub ListRecentFiles()
    Dim i As Integer
    Dim rng As Excel.Range = DirectCast( _
        ThisApplication.Range("RecentFiles"). _
        Cells(1, 1), Excel.Range)
    For i = 1 To ThisApplication.RecentFiles.Count
        rng.Offset(i - 1, 0).Value = _
            ThisApplication.RecentFiles(i).Name
    Next
End Sub

// C#
private void ListRecentFiles()
{
    Excel.Range rng = (Excel.Range)ThisApplication.
        get_Range("RecentFiles", Type.Missing).Cells[1, 1];

    for (int i = 1; i <= ThisApplication.RecentFiles.Count; i++)
    {
        rng.get_Offset(i - 1, 0).Value2 = 
            ThisApplication.RecentFiles[i].Name;
    } 
}

**Tip   **The Range.Cells property looks like it's a parameterized property, but it's not—the Cells property returns an array, and you index into the array by passing values for the row and column numbers.

If you want to work with the various file-handling dialog boxes provided by Excel, you'll need to again use an accessor method: get_FileDialog. Because the Application.FileDialog property has a parameter, it's not directly available from C#. To avoid this limitation, you can call get_FileDialog as in the following fragment:

' Visual Basic
With ThisApplication.FileDialog( _
    Office.MsoFileDialogType.msoFileDialogFolderPicker)
    If .Show <> 0 Then
        ThisApplication.Range("FolderPickerResults"). _
            Value = .SelectedItems.Item(1)
    End If
End With

// C#
dlg = ThisApplication.get_FileDialog(
    Office.MsoFileDialogType.msoFileDialogFolderPicker);
if (dlg.Show() != 0)
{
    ThisApplication.get_Range("FolderPickerResults", Type.Missing).
        Value2 = dlg.SelectedItems.Item(1);
}

The Excel Range.Value property also requires special handling. This property accepts a parameter, but C# cannot accept parameters when setting or retrieving property values. In this case, C# developers must use the Value2 property, a variant of the original property that doesn't expect a parameter. For example, the following fragments show equivalent Visual Basic and C# code:

' Visual Basic
Dim rng As Excel.Range = ThisApplication.Range("A1")
rng.Value = "Some Value"

// C#
Excel.Range rng = ThisApplication.get_Range("A1", Type.Missing);
rng.Value2 = "Some Value";

You've seen examples of use the Range, Offset, Value, and FileDialog properties—there are many others, as well. If you find that you can't retrieve or set a property's value directly, check to see if the property is expecting a parameter, and if so, look for the corresponding accessor methods or an alternative property.

Using Accessor Methods in Word

Although Word doesn't generally use parameterized properties, as does Excel, there are still cases when programming Word in which you'll need to use the get_* and set_* accessor methods. Word handles these methods slightly differently than does Excel, however—in this case, the accessor methods are hidden. You won't find them in the list of available methods when you're typing, and you won't find them in the Object Browser window. You simply must "know" that they're there, and use them.

For example, the Style property in Word returns and accepts a Variant value, and rather than converting this to an Object type for use in C#, you must instead call the get_Style and set_Style methods to work around this problem. You might write code like the following to set the style for a particular paragraph:

' Visual Basic
Dim rng As Word.Range = _
    ThisDocument.Paragraphs(1).Range
rng.Style = "Normal Indent"

// C#
Word.Range rng = ThisDocument.Paragraphs[1].Range;
Object style = "Normal Indent";
rng.set_Style(ref style);

As with other methods in Word, you must pass the style name (or Style object) by reference, requiring the use of a variable containing the desired value. You can use similar code to retrieve a range's Style property, calling the get_Style method.

The Item method in Word poses a similar challenge. Rather than allowing you to pass a Variant parameter to the method, the Word PIA exposes a get_Item method that accepts an Object parameter by reference, instead. If you want to save a particular document within the current Documents collection, you could write code as in the following fragment:

' Visual Basic
ThisApplication.Documents("MyNewDocument.doc").Save()

// C#
Object file = "MyNewDocument.doc";
ThisApplication.Documents.get_Item(ref file).Save();

The rule is simple: for any method that accepts a Variant parameter, or any parameterized property, you'll need to assume that there's an accessor method (or pair of methods) available. Even though you won't see the methods in the IntelliSense list, type the name (set_* or get_*) anyway—once you do, you'll see a ToolTip indicating the appropriate parameters.

Late Binding in Word

Because the Word programming model has a long career, some of its techniques don't really fit in an object-oriented, strongly typed world. One prime example is the Dialog class—this class provides a base set of properties and methods, and an array containing all the available dialog boxes that you see from within the Word user interface. You can display any of the dialog boxes, or you can simply use the various properties exposed by the dialog boxes to affect Word behavior. For example, you use the Page Setup dialog box to configure page width and height for the current document.

The problem is that the Word object model doesn't really contain information about each specific dialog box. There are a huge number of properties associated with the dialog boxes, and instead of hard-coding the properties in the type library (and now the PIA) for Word, the object model creates the properties "on the fly" as you load each dialog box. Basically, each control on each dialog box has a corresponding property, and you can set or retrieve the value of each control programmatically. In VBA, this wasn't a problem. Although you wouldn't necessarily get IntelliSense hints to help you type the code, you could refer to any of the various properties and your code would still compile.

Not so, in Visual Basic .NET (with Option Strict enabled) or in C#. In strongly typed languages, you cannot expect the compiler to disregard the available properties until run time, if you intend to use the standard Object.Property syntax when you write the code. For example, the following code works fine in Visual Basic .NET with Option Strict Off, but the corresponding C# code simply won't compile:

' Visual Basic .NET with Option Strict Off
Dim dlg As Word.Dialog
dlg = ThisApplication.Dialogs( _
    Word.WdWordDialog.wdDialogFilePageSetup)
dlg.PageWidth = 3.3
dlg.PageHeight = 6

// C# (This won't compile!)
dlg = ThisApplication.Dialogs
    [Word.WdWordDialog.wdDialogFilePageSetup];

dlg.PageWidth = 3.3;
dlg.PageHeight = 6;

You have two options for working with these properties: you can either create a Visual Basic .NET file that includes the Option Strict Off setting at the top and place your code in that file, or you can find a way to perform late binding. Both C# and Visual Basic .NET developers can take advantage of the System.Reflection namespace, which allows running code to determine available members of a specified type and performing a type of late binding. Given the member information, your code can use the System.Reflection namespace to invoke the property set procedure, for example, to set a property value.

In order to handle late-bound objects from within Visual Basic .NET or C#, you can add a simple wrapper around the System.Reflection namespace's ability to set a property of an object whose capabilities aren't known until run time:

' Visual Basic
Private Sub InvokeHelper(ByVal dlg As Word.Dialog, _
 ByVal member As String, ByVal dlgValue As Object)
  Dim dlgType As Type = GetType(Word.Dialog)
    dlgType.InvokeMember(member, _
     BindingFlags.SetProperty Or _
     BindingFlags.Public Or BindingFlags.Instance, _
     Nothing, dlg, New Object() {dlgValue})
End Sub
// C#
private void invokeHelper(Word.Dialog dlg, string member, Object dlgValue) 
{ 
  // Assumes a using statement in the file:
  // using System.Reflection;
  Type dlgType = typeof(Word.Dialog); 
  dlgType.InvokeMember(member, 
    BindingFlags.SetProperty | 
    BindingFlags.Public | 
    BindingFlags.Instance, 
    null, dlg, new object[] {dlgValue.ToString()}); 
}

In order to take advantage of the InvokeHelper method, pass in the Dialog object, the property to be set, and the value for the property, like this:

' Visual Basic
Public Sub HiddenPageSetupDialog()
    Dim dlg As Word.Dialog
    dlg = ThisApplication.Dialogs( _
        Word.WdWordDialog.wdDialogFilePageSetup)

    InvokeHelper(dlg, "PageWidth", 3.3)
    InvokeHelper(dlg, "PageHeight", 6)
    dlg.Execute();
End Sub
// C#
public void HiddenPageSetupDialog()
{
    Word.Dialog dlg;
    dlg = ThisApplication.Dialogs[
        Word.WdWordDialog.wdDialogFilePageSetup];
    invokeHelper(dlg, "PageWidth", 3.3);
    invokeHelper(dlg, "PageHeight", 6);
    dlg.Execute();
}

Summary

Microsoft Office Word and Microsoft Office Excel expose rich programming models, but those object models were originally written to be consumed by VBA clients. Visual Basic .NET, even with Option Strict On, can handle most of the requirements of the Office object models. Consuming those object models with C# requires a bit more care. When writing applications using Visual Studio Tools for the Microsoft Office System and C#, keep in mind that you'll often need to make concessions to the differences between the languages, watching for optional parameters, parameterized properties, Variants, and late binding. Once you internalize the types of issues you'll face, writing C# code that interacts with Office becomes no more difficult than writing Visual Basic .NET code.