Excel interop: _Worksheet or Worksheet?

I'm currently writing about dynamic typing, and I'm giving an example of Excel interop. I've hardly done any Office interop before, and it shows. The MSDN Office Interop tutorial for C# 4 uses the _Worksheet interface, but there's also a Worksheet interface. I've no idea what the difference is.

In my absurdly simple demo app (shown below) either works fine - but if best practice dictates one or the other, I'd rather use it appropriately.

using System;
using System.Linq;
using Excel = Microsoft.Office.Interop.Excel;

class DynamicExcel
{
    static void Main()
    {
        var app = new Excel.Application { Visible = true };
        app.Workbooks.Add();

        // Can use Excel._Worksheet instead here. Which is better?
        Excel.Worksheet workSheet = app.ActiveSheet;

        Excel.Range start = workSheet.Cells[1, 1];
        Excel.Range end = workSheet.Cells[1, 20];
        workSheet.get_Range(start, end).Value2 = Enumerable.Range(1, 20)
                                                           .ToArray();
    }
}

I'm trying to avoid doing a full deep-dive into COM or Office interoperability, just highlighting the new features of C# 4 - but I don't want to do anything really, really dumb.

(There may be something really, really dumb in the code above as well, in which case please let me know. Using separate start/end cells instead of just "A1:T1" is deliberate - it's easier to see that it's genuinely a range of 20 cells. Anything else is probably accidental.)

So, should I use _Worksheet or Worksheet, and why?


Solution 1:

If I recall correctly -- and my memory on this is a bit fuzzy, it has been a long time since I took the Excel PIA apart -- it's like this.

An event is essentially a method that an object calls when something happens. In .NET, events are delegates, plain and simple. But in COM, it is very common to organize a whole bunch of event callbacks into interfaces. You therefore have two interfaces on a given object -- the "incoming" interface, the methods you expect other people to call on you, and the "outgoing" interface, the methods you expect to call on other people when events happen.

In the unmanaged metadata -- the type library -- for a creatable object there are definitions for three things: the incoming interface, the outgoing interface, and the coclass, which says "I'm a creatable object that implements this incoming interface and this outgoing interface".

Now when the type library is automatically translated into metadata, those relationships are, sadly, preserved. It would have been nicer to have a hand-generated PIA that made the classes and interfaces conform more to what we'd expect in the managed world, but sadly, that didn't happen. Therefore the Office PIA is full of these seemingly odd duplications, where every creatable object seems to have two interfaces associated with it, with the same stuff on them. One of the interfaces represents the interface to the coclass, and one of them represents the incoming interface to that coclass.

The _Workbook interface is the incoming interface on the workbook coclass. The Workbook interface is the interface which represents the coclass itself, and therefore inherits from _Workbook.

Long story short, I would use Workbook if you can do so conveniently; _Workbook is a bit of an implementation detail.

Solution 2:

If you look at the PIA assembly (Microsoft.Office.Interop.Excel) in Reflector, the Workbook interface has this definition ...

public interface Workbook : _Workbook, WorkbookEvents_Event

Workbook is _Workbook but adds events. Same for Worksheet (sorry, just noticed you were not talking about Workbooks) ...

public interface Worksheet : _Worksheet, DocEvents_Event

DocEvents_Event ...

[ComVisible(false), TypeLibType((short) 0x10), ComEventInterface(typeof(DocEvents),
                     typeof(DocEvents_EventProvider))]
public interface DocEvents_Event
{
    // Events
    event DocEvents_ActivateEventHandler Activate;
    event DocEvents_BeforeDoubleClickEventHandler BeforeDoubleClick;
    event DocEvents_BeforeRightClickEventHandler BeforeRightClick;
    event DocEvents_CalculateEventHandler Calculate;
    event DocEvents_ChangeEventHandler Change;
    event DocEvents_DeactivateEventHandler Deactivate;
    event DocEvents_FollowHyperlinkEventHandler FollowHyperlink;
    event DocEvents_PivotTableUpdateEventHandler PivotTableUpdate;
    event DocEvents_SelectionChangeEventHandler SelectionChange;
}

I would say it's best bet to use Worksheet, but that's the difference.

Solution 3:

Classes and Interfaces for Internal Use Only

Avoid directly using any of the following classes and interfaces, which are used internally and are typically not used directly.

Class/Interface : Examples

classid Class : ApplicationClass (Word or Excel), WorksheetClass (Excel)

classid Events x _SinkHelper : ApplicationEvents4_SinkHelper (Word), WorkbookEvents_SinkHelper (Excel)

_classid : _Application (Word or Excel), _Worksheet (Excel)

classid Events x : ApplicationEvents4 (Word), AppEvents (Excel)

I classid Events x : IApplicationEvents4 (Word), IAppEvents (Excel)

http://msdn.microsoft.com/en-gb/library/ms247299(office.11).aspx

edit: (re: formatting of this answer) cannot correctly format an escaped underscore followed immediately by italic text. Shows correctly in preview but broken when posted

edit2: works if you make the underscore itself italic which is conceptually horrible but looks the same I suppose

Solution 4:

I have seen and written quite a bit of C# / Excel COM Interop code over the last few years and I've seen Worksheet used in almost every case. I have never seen anything definitive from Microsoft on the subject.

Solution 5:

MSDN shows that the Worksheet interface simply inherits from the _Worksheet and DocEvents_Event interfaces. It would seem that one simply provides the events that a worksheet object might raise in additional to everything else. As far as I can see, Worksheet doesn't provide any other members of its own. So yeah, you might as well just go with using the Worksheet interface in all cases, since you don't lose anything by it, and potentially might need the events it exposes.